Updating server dependancies (#5249)

This commit is contained in:
Christopher Speller
2017-02-02 09:32:00 -05:00
committed by Harrison Healey
parent ca3211bc04
commit 701d1ab638
326 changed files with 121334 additions and 3928 deletions

View File

@@ -31,7 +31,7 @@ func InitFile() {
BaseRoutes.NeedFile.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("GET")
BaseRoutes.Public.Handle("/files/{file_id:[A-Za-z0-9]+}/get", ApiAppHandlerTrustRequesterIndependent(getPublicFile)).Methods("GET")
BaseRoutes.Public.Handle("/files/get/{team_id:[A-Za-z0-9]+}/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandlerTrustRequesterIndependent(getPublicFileOld)).Methods("GET")
BaseRoutes.Public.Handle("/files/get/{team_id:[A-Za-z0-9]+}/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:(?:[A-Za-z0-9]+/)?.+(?:\\.[A-Za-z0-9]{3,})?}", ApiAppHandlerTrustRequesterIndependent(getPublicFileOld)).Methods("GET")
}
func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {

54
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 67ac70374ac7d1acb02736f628409e406cb55f343a260ccf78c4f324ec2df45a
updated: 2016-12-25T21:59:49.671116871+05:30
hash: 42839eb256dd5b5607d93cd50372cf55881de6e874a63525862ed3f0ac47682b
updated: 2017-02-01T10:22:19.702548922-05:00
imports:
- name: github.com/alecthomas/log4go
version: e5dc62318d9bd58682f1dceb53a4b24e8253682f
@@ -10,7 +10,7 @@ imports:
- name: github.com/dgryski/dgoogauth
version: 96977cbd42e27be71f9f731db6634123de7e861a
- name: github.com/disintegration/imaging
version: 5b7e22645c93e3f3911b36b7d66bf8799f3eddfd
version: 243d2d8673c1225a6afceeb9b3b4423d485dc8df
- name: github.com/dyatlov/go-opengraph
version: 41a3523719dfbe7e8f853fbd4061867543db5270
subpackages:
@@ -18,16 +18,16 @@ imports:
- name: github.com/go-gorp/gorp
version: 0c9bc0918534d133cedb439a24adc7cbe66e4a9d
- name: github.com/go-ldap/ldap
version: d0a5ced67b4dc310b9158d63a2c6f9c5ec13f105
version: 8168ee085ee43257585e50c6441aadf54ecb2c9f
- name: github.com/go-sql-driver/mysql
version: a732e14c62dde3285440047bba97581bc472ae18
version: a0583e0143b1624142adab07e0e97fe106d99561
- name: github.com/goamz/goamz
version: fb002ae75f50beb93874fa6ce2c861e488ce08a0
version: b2c2eaf25cbb87f41087aa796facf82113809ed5
subpackages:
- aws
- s3
- name: github.com/golang/freetype
version: 38b4c392adc5eed94207994c4848fff99f4ac234
version: d9be45aaf7452cc30c0ceb1b1bf7efe1d17b7c87
subpackages:
- raster
- truetype
@@ -38,11 +38,11 @@ imports:
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/handlers
version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
version: 3a5767ca75ece5f7f1440b1d16975247f8d8b221
- name: github.com/gorilla/mux
version: 34dda716af12ba79ed40ce1f66b102ff75dc3411
version: 392c28fe23e1c45ddba891b0320b3b5df220beea
- name: github.com/gorilla/websocket
version: e8f0f8aaa98dfb6586cbdf2978d511e3199a960a
version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13
- name: github.com/hashicorp/golang-lru
version: 0a025b7e63adc15a622f29b0b2c4c3848243bbf6
subpackages:
@@ -52,7 +52,7 @@ imports:
- name: github.com/jehiah/go-strftime
version: 834e15c05a45371503440cc195bbd05c9a0968d9
- name: github.com/lib/pq
version: d8eeeb8bae8896dd8e1b7e514ab0d396c4f12a1b
version: a6657b2386e9b8be76484c08711b02c7cf867ead
subpackages:
- oid
- name: github.com/mattermost/rsc
@@ -66,23 +66,25 @@ imports:
subpackages:
- pbutil
- name: github.com/miekg/dns
version: 271c58e0c14f552178ea321a545ff9af38930f39
version: 99f84ae56e75126dd77e5de4fae2ea034a468ca1
- name: github.com/minio/minio-go
version: c040578255dbb1a1f41cbdc5bd944de4140b4149
version: 52cc94e879db78c2e2c6e160869df943137ec4cd
subpackages:
- pkg/policy
- pkg/s3signer
- pkg/s3utils
- pkg/set
- name: github.com/mssola/user_agent
version: 85b2f5798558a46fc23443c596e781712f4b7792
- name: github.com/nicksnyder/go-i18n
version: 1c9e01733308fe876a840a54ce7633540b182a75
version: f757c9f9b69c16ff69d38dbf224be28a7b6537bb
subpackages:
- i18n
- i18n/bundle
- i18n/language
- i18n/translation
- name: github.com/NYTimes/gziphandler
version: 172fbbbb329cf7e031dd3ab35766186fc2081eab
version: 6710af535839f57c687b62c4c23d649f9545d885
- name: github.com/pborman/uuid
version: a97ce2ca70fa5a848076093f05e639a89ca34d06
- name: github.com/prometheus/client_golang
@@ -94,11 +96,11 @@ imports:
subpackages:
- go
- name: github.com/prometheus/common
version: 0d5de9d6d8629cb8bee6d4674da4127cd8b615a3
version: dd2f054febf4a6c00f2343686efb775948a8bff4
subpackages:
- expfmt
- name: github.com/prometheus/procfs
version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
version: 1878d9fbb537119d24b21ca07effd591627cd160
- name: github.com/rsc/letsencrypt
version: 76104d26167d38b6a0010f42bfc8ec5487742e8b
- name: github.com/rwcarlsen/goexif
@@ -111,25 +113,25 @@ imports:
- name: github.com/segmentio/backo-go
version: 204274ad699c0983a70203a566887f17a717fef4
- name: github.com/spf13/cobra
version: 9495bc009a56819bdb0ddbc1a373e29c140bc674
version: 35136c09d8da66b901337c6e86fd8e88a1a255bd
- name: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7
- name: github.com/tylerb/graceful
version: 4df1190835320af7076dfcf27b3d071fd3612caf
version: 0e9129e9c6d47da90dc0c188b26bd7bb1dab53cd
- name: github.com/xenolf/lego
version: 0abbd738a45af51595a214c52b02b18d16974f19
version: f5d538caab6dc0c167d4e32990c79bbf9eff578c
subpackages:
- acme
- name: github.com/xtgo/uuid
version: a0b114877d4caeffbd7f87e3757c17fce570fea7
- name: golang.org/x/crypto
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
version: dc137beb6cce2043eb6b5f223ab8bf51c32459f4
subpackages:
- bcrypt
- blowfish
- ocsp
- name: golang.org/x/image
version: 507b1a44bd72830c136e6ecf7c05f9089796d572
version: 83686c547965220f8b5d75e83ddc67d73420a89f
subpackages:
- bmp
- font
@@ -137,14 +139,14 @@ imports:
- tiff
- tiff/lzw
- name: golang.org/x/net
version: 4971afdc2f162e82d185353533d3cf16188a9f4e
version: f2499483f923065a842d38eb4c7f1927e6fc6e6d
subpackages:
- context
- html
- html/atom
- publicsuffix
- name: golang.org/x/sys
version: 30237cf4eefd639b184d1f2cb77a581ea0be8947
version: 7a6e5648d140666db5d920909e082ca00a87ba2c
subpackages:
- unix
- name: golang.org/x/time
@@ -166,5 +168,5 @@ imports:
- store
- store/memstore
- name: gopkg.in/yaml.v2
version: a5b47d31c556af34a302ce5d659e6fea44d90de0
version: 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
testImports: []

View File

@@ -1,56 +1,45 @@
package: github.com/mattermost/platform
import:
- package: github.com/NYTimes/gziphandler
version: 172fbbbb329cf7e031dd3ab35766186fc2081eab
- package: github.com/alecthomas/log4go
version: e5dc62318d9bd58682f1dceb53a4b24e8253682f
- package: github.com/dgryski/dgoogauth
version: 96977cbd42e27be71f9f731db6634123de7e861a
- package: github.com/disintegration/imaging
version: 5b7e22645c93e3f3911b36b7d66bf8799f3eddfd
- package: github.com/go-gorp/gorp
version: 0c9bc0918534d133cedb439a24adc7cbe66e4a9d
- package: github.com/go-ldap/ldap
version: v2.4.1
version: v2.5.0
- package: github.com/go-sql-driver/mysql
version: v1.2
version: v1.3
- package: github.com/goamz/goamz
version: fb002ae75f50beb93874fa6ce2c861e488ce08a0
subpackages:
- aws
- s3
- package: github.com/golang/freetype
version: 38b4c392adc5eed94207994c4848fff99f4ac234
- package: github.com/gorilla/handlers
version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
version: v1.2
- package: github.com/gorilla/mux
version: 34dda716af12ba79ed40ce1f66b102ff75dc3411
version: v1.3.0
- package: github.com/gorilla/websocket
version: e8f0f8aaa98dfb6586cbdf2978d511e3199a960a
version: v1.1.0
- package: github.com/lib/pq
version: d8eeeb8bae8896dd8e1b7e514ab0d396c4f12a1b
- package: github.com/mattermost/rsc
version: bbaefb05eaa0389ea712340066837c8ce4d287f9
subpackages:
- qr
- package: github.com/mssola/user_agent
version: v0.4.1
- package: github.com/nicksnyder/go-i18n
version: v1.6.0
version: v1.7.0
subpackages:
- i18n
- package: github.com/pborman/uuid
version: v1.0
- package: github.com/rwcarlsen/goexif
version: 709fab3d192d7c62f86043caff1e7e3fb0f42bd8
subpackages:
- exif
- package: golang.org/x/crypto
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
subpackages:
- bcrypt
- package: golang.org/x/image
version: 507b1a44bd72830c136e6ecf7c05f9089796d572
subpackages:
- bmp
- package: gopkg.in/fsnotify.v1
@@ -62,9 +51,8 @@ import:
- package: github.com/segmentio/analytics-go
version: 2.1.1
- package: github.com/rsc/letsencrypt
version: 76104d26167d38b6a0010f42bfc8ec5487742e8b
- package: github.com/minio/minio-go
version: 2.0.2
version: v2.0.3
- package: github.com/prometheus/client_golang
version: v0.8.0
subpackages:
@@ -89,6 +77,5 @@ import:
- package: github.com/spf13/cobra
- package: github.com/spf13/pflag
- package: github.com/dyatlov/go-opengraph
version: 41a3523719dfbe7e8f853fbd4061867543db5270
subpackages:
- opengraph

View File

@@ -1,8 +1,10 @@
package gziphandler
import (
"bufio"
"compress/gzip"
"fmt"
"net"
"net/http"
"strconv"
"strings"
@@ -131,6 +133,18 @@ func (w *GzipResponseWriter) Flush() {
}
}
// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
// Hijacker, its Hijack method is returned. Otherwise an error is returned.
func (w *GzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hj, ok := w.ResponseWriter.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, fmt.Errorf("http.Hijacker interface is not supported")
}
// verify Hijacker interface implementation
var _ http.Hijacker = &GzipResponseWriter{}
// MustNewGzipLevelHandler behaves just like NewGzipLevelHandler except that in
// an error case it panics rather than returning an error.
func MustNewGzipLevelHandler(level int) func(http.Handler) http.Handler {

View File

@@ -67,15 +67,16 @@ func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
for ix := start; ix <= end; ix++ {
weight := kernel[absint(x-ix)]
i := y*src.Stride + ix*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
wa := float64(src.Pix[i+3]) * weight
r += float64(src.Pix[i+0]) * wa
g += float64(src.Pix[i+1]) * wa
b += float64(src.Pix[i+2]) * wa
a += wa
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
r = math.Min(math.Max(r/a, 0.0), 255.0)
g = math.Min(math.Max(g/a, 0.0), 255.0)
b = math.Min(math.Max(b/a, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4
@@ -121,15 +122,16 @@ func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
for iy := start; iy <= end; iy++ {
weight := kernel[absint(y-iy)]
i := iy*src.Stride + x*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
wa := float64(src.Pix[i+3]) * weight
r += float64(src.Pix[i+0]) * wa
g += float64(src.Pix[i+1]) * wa
b += float64(src.Pix[i+2]) * wa
a += wa
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
r = math.Min(math.Max(r/a, 0.0), 255.0)
g = math.Min(math.Max(g/a, 0.0), 255.0)
b = math.Min(math.Max(b/a, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4

View File

@@ -50,9 +50,9 @@ func TestBlur(t *testing.T) {
Rect: image.Rect(0, 0, 3, 3),
Stride: 3 * 4,
Pix: []uint8{
0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
0x09, 0x10, 0x18, 0x18, 0x3f, 0x69, 0x9e, 0x9e, 0x09, 0x10, 0x18, 0x18,
0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
0x66, 0xaa, 0xff, 0x04, 0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x04,
0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x9e, 0x66, 0xaa, 0xff, 0x18,
0x66, 0xaa, 0xff, 0x04, 0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x04,
},
},
},
@@ -72,9 +72,9 @@ func TestBlur(t *testing.T) {
Rect: image.Rect(0, 0, 3, 3),
Stride: 3 * 4,
Pix: []uint8{
0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
},
},
},
@@ -134,7 +134,7 @@ func TestSharpen(t *testing.T) {
Stride: 3 * 4,
Pix: []uint8{
0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7e, 0x7e, 0x64, 0x64, 0x64, 0x64,
0x64, 0x64, 0x64, 0x64, 0x7d, 0x7d, 0x7d, 0x7e, 0x64, 0x64, 0x64, 0x64,
0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
},
},

43
vendor/github.com/disintegration/imaging/histogram.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package imaging
import (
"image"
)
// Histogram returns a normalized histogram of an image.
//
// Resulting histogram is represented as an array of 256 floats, where
// histogram[i] is a probability of a pixel being of a particular luminance i.
func Histogram(img image.Image) [256]float64 {
src := toNRGBA(img)
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
var histogram [256]float64
var total float64
if width == 0 || height == 0 {
return histogram
}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
i := y*src.Stride + x*4
r := src.Pix[i+0]
g := src.Pix[i+1]
b := src.Pix[i+2]
var y float32 = 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b)
histogram[int(y+0.5)]++
total++
}
}
for i := 0; i < 256; i++ {
histogram[i] = histogram[i] / total
}
return histogram
}

View File

@@ -0,0 +1,42 @@
package imaging
import (
"image"
"image/color"
"testing"
)
func TestHistogram(t *testing.T) {
b := image.Rectangle{image.Point{0, 0}, image.Point{2, 2}}
i1 := image.NewRGBA(b)
i1.Set(0, 0, image.Black)
i1.Set(1, 0, image.White)
i1.Set(1, 1, image.White)
i1.Set(0, 1, color.Gray{123})
h := Histogram(i1)
if h[0] != 0.25 || h[123] != 0.25 || h[255] != 0.5 {
t.Errorf("Incorrect histogram for image i1")
}
i2 := image.NewRGBA(b)
i2.Set(0, 0, color.Gray{51})
i2.Set(0, 1, color.Gray{14})
i2.Set(1, 0, color.Gray{14})
h = Histogram(i2)
if h[14] != 0.5 || h[51] != 0.25 || h[0] != 0.25 {
t.Errorf("Incorrect histogram for image i2")
}
b = image.Rectangle{image.Point{0, 0}, image.Point{0, 0}}
i3 := image.NewRGBA(b)
h = Histogram(i3)
for _, val := range h {
if val != 0 {
t.Errorf("Histogram for an empty image should be a zero histogram.")
return
}
}
}

View File

@@ -128,20 +128,21 @@ func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
var c [4]int32
var c [4]int64
for _, iw := range weights[dstX].iwpairs {
i := dstY*src.Stride + iw.i*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
a := int64(src.Pix[i+3]) * int64(iw.w)
c[0] += int64(src.Pix[i+0]) * a
c[1] += int64(src.Pix[i+1]) * a
c[2] += int64(src.Pix[i+2]) * a
c[3] += a
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstX].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
}
}
})
@@ -165,20 +166,21 @@ func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.
for dstX := partStart; dstX < partEnd; dstX++ {
for dstY := 0; dstY < dstH; dstY++ {
var c [4]int32
var c [4]int64
for _, iw := range weights[dstY].iwpairs {
i := iw.i*src.Stride + dstX*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
a := int64(src.Pix[i+3]) * int64(iw.w)
c[0] += int64(src.Pix[i+0]) * a
c[1] += int64(src.Pix[i+1]) * a
c[2] += int64(src.Pix[i+2]) * a
c[3] += a
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstY].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
}
}

View File

@@ -28,7 +28,7 @@ func TestResize(t *testing.T) {
&image.NRGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{0x40, 0x40, 0x40, 0xc0},
Pix: []uint8{0x55, 0x55, 0x55, 0xc0},
},
},
{
@@ -108,10 +108,10 @@ func TestResize(t *testing.T) {
Rect: image.Rect(0, 0, 4, 4),
Stride: 4 * 4,
Pix: []uint8{
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0xbf, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xff,
0x00, 0x40, 0x00, 0x40, 0x30, 0x30, 0x10, 0x70, 0x8f, 0x10, 0x30, 0xcf, 0xbf, 0x00, 0x40, 0xff,
0x00, 0xbf, 0x00, 0xbf, 0x10, 0x8f, 0x30, 0xcf, 0x30, 0x30, 0x8f, 0xef, 0x40, 0x00, 0xbf, 0xff,
0x00, 0xff, 0x00, 0xff, 0x00, 0xbf, 0x40, 0xff, 0x00, 0x40, 0xbf, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xff,
0x00, 0xff, 0x00, 0x3f, 0x6d, 0x6e, 0x24, 0x6f, 0xb1, 0x13, 0x3a, 0xd0, 0xc0, 0x00, 0x3f, 0xff,
0x00, 0xff, 0x00, 0xc0, 0x13, 0xb2, 0x3a, 0xcf, 0x33, 0x32, 0x9a, 0xef, 0x3f, 0x00, 0xc0, 0xff,
0x00, 0xff, 0x00, 0xff, 0x00, 0xc0, 0x3f, 0xff, 0x00, 0x3f, 0xc0, 0xff, 0x00, 0x00, 0xff, 0xff,
},
},
},
@@ -224,7 +224,7 @@ func TestFit(t *testing.T) {
&image.NRGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{0x40, 0x40, 0x40, 0xc0},
Pix: []uint8{0x55, 0x55, 0x55, 0xc0},
},
},
{
@@ -242,7 +242,7 @@ func TestFit(t *testing.T) {
&image.NRGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{0x40, 0x40, 0x40, 0xc0},
Pix: []uint8{0x55, 0x55, 0x55, 0xc0},
},
},
{
@@ -512,7 +512,7 @@ func TestThumbnail(t *testing.T) {
&image.NRGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{0x40, 0x40, 0x40, 0xc0},
Pix: []uint8{0x55, 0x55, 0x55, 0xc0},
},
},
{
@@ -534,7 +534,7 @@ func TestThumbnail(t *testing.T) {
&image.NRGBA{
Rect: image.Rect(0, 0, 1, 1),
Stride: 1 * 4,
Pix: []uint8{0x40, 0x40, 0x40, 0xc0},
Pix: []uint8{0x55, 0x55, 0x55, 0xc0},
},
},
{

View File

@@ -1,15 +1,20 @@
language: go
env:
global:
- VET_VERSIONS="1.5 1.6 tip"
- LINT_VERSIONS="1.5 1.6 tip"
- VET_VERSIONS="1.6 1.7 tip"
- LINT_VERSIONS="1.6 1.7 tip"
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- tip
matrix:
fast_finish: true
allow_failures:
- go: tip
go_import_path: gopkg.in/ldap.v2
install:
- go get gopkg.in/asn1-ber.v1

View File

@@ -1,27 +1,22 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
The MIT License (MIT)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
Portions copyright (c) 2015-2016 go-ldap Authors
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -334,18 +334,18 @@ func DecodeControl(packet *ber.Packet) Control {
for _, child := range sequence.Children {
if child.Tag == 0 {
//Warning
child := child.Children[0]
packet := ber.DecodePacket(child.Data.Bytes())
warningPacket := child.Children[0]
packet := ber.DecodePacket(warningPacket.Data.Bytes())
val, ok := packet.Value.(int64)
if ok {
if child.Tag == 0 {
if warningPacket.Tag == 0 {
//timeBeforeExpiration
c.Expire = val
child.Value = c.Expire
} else if child.Tag == 1 {
warningPacket.Value = c.Expire
} else if warningPacket.Tag == 1 {
//graceAuthNsRemaining
c.Grace = val
child.Value = c.Grace
warningPacket.Value = c.Grace
}
}
} else if child.Tag == 1 {

View File

@@ -56,3 +56,42 @@ func runControlTest(t *testing.T, originalControl Control) {
t.Errorf("%sgot different type decoding from encoded bytes: %T vs %T", header, fromBytes, originalControl)
}
}
func TestDescribeControlManageDsaIT(t *testing.T) {
runAddControlDescriptions(t, NewControlManageDsaIT(false), "Control Type (Manage DSA IT)")
runAddControlDescriptions(t, NewControlManageDsaIT(true), "Control Type (Manage DSA IT)", "Criticality")
}
func TestDescribeControlPaging(t *testing.T) {
runAddControlDescriptions(t, NewControlPaging(100), "Control Type (Paging)", "Control Value (Paging)")
runAddControlDescriptions(t, NewControlPaging(0), "Control Type (Paging)", "Control Value (Paging)")
}
func TestDescribeControlString(t *testing.T) {
runAddControlDescriptions(t, NewControlString("x", true, "y"), "Control Type ()", "Criticality", "Control Value")
runAddControlDescriptions(t, NewControlString("x", true, ""), "Control Type ()", "Criticality", "Control Value")
runAddControlDescriptions(t, NewControlString("x", false, "y"), "Control Type ()", "Control Value")
runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()", "Control Value")
}
func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) {
header := ""
if callerpc, _, line, ok := runtime.Caller(1); ok {
if caller := runtime.FuncForPC(callerpc); caller != nil {
header = fmt.Sprintf("%s:%d: ", caller.Name(), line)
}
}
encodedControls := encodeControls([]Control{originalControl})
addControlDescriptions(encodedControls)
encodedPacket := encodedControls.Children[0]
if len(encodedPacket.Children) != len(childDescriptions) {
t.Errorf("%sinvalid number of children: %d != %d", header, len(encodedPacket.Children), len(childDescriptions))
}
for i, desc := range childDescriptions {
if encodedPacket.Children[i].Description != desc {
t.Errorf("%sdescription not as expected: %s != %s", header, encodedPacket.Children[i].Description, desc)
}
}
}

96
vendor/github.com/go-ldap/ldap/dn.go generated vendored
View File

@@ -83,9 +83,19 @@ func ParseDN(str string) (*DN, error) {
attribute := new(AttributeTypeAndValue)
escaping := false
unescapedTrailingSpaces := 0
stringFromBuffer := func() string {
s := buffer.String()
s = s[0 : len(s)-unescapedTrailingSpaces]
buffer.Reset()
unescapedTrailingSpaces = 0
return s
}
for i := 0; i < len(str); i++ {
char := str[i]
if escaping {
unescapedTrailingSpaces = 0
escaping = false
switch char {
case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
@@ -107,10 +117,10 @@ func ParseDN(str string) (*DN, error) {
buffer.WriteByte(dst[0])
i++
} else if char == '\\' {
unescapedTrailingSpaces = 0
escaping = true
} else if char == '=' {
attribute.Type = buffer.String()
buffer.Reset()
attribute.Type = stringFromBuffer()
// Special case: If the first character in the value is # the
// following data is BER encoded so we can just fast forward
// and decode.
@@ -133,7 +143,7 @@ func ParseDN(str string) (*DN, error) {
}
} else if char == ',' || char == '+' {
// We're done with this RDN or value, push it
attribute.Value = buffer.String()
attribute.Value = stringFromBuffer()
rdn.Attributes = append(rdn.Attributes, attribute)
attribute = new(AttributeTypeAndValue)
if char == ',' {
@@ -141,8 +151,17 @@ func ParseDN(str string) (*DN, error) {
rdn = new(RelativeDN)
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
}
buffer.Reset()
} else if char == ' ' && buffer.Len() == 0 {
// ignore unescaped leading spaces
continue
} else {
if char == ' ' {
// Track unescaped spaces in case they are trailing and we need to remove them
unescapedTrailingSpaces++
} else {
// Reset if we see a non-space char
unescapedTrailingSpaces = 0
}
buffer.WriteByte(char)
}
}
@@ -150,9 +169,76 @@ func ParseDN(str string) (*DN, error) {
if len(attribute.Type) == 0 {
return nil, errors.New("DN ended with incomplete type, value pair")
}
attribute.Value = buffer.String()
attribute.Value = stringFromBuffer()
rdn.Attributes = append(rdn.Attributes, attribute)
dn.RDNs = append(dn.RDNs, rdn)
}
return dn, nil
}
// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
// Returns true if they have the same number of relative distinguished names
// and corresponding relative distinguished names (by position) are the same.
func (d *DN) Equal(other *DN) bool {
if len(d.RDNs) != len(other.RDNs) {
return false
}
for i := range d.RDNs {
if !d.RDNs[i].Equal(other.RDNs[i]) {
return false
}
}
return true
}
// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
func (d *DN) AncestorOf(other *DN) bool {
if len(d.RDNs) >= len(other.RDNs) {
return false
}
// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
for i := range d.RDNs {
if !d.RDNs[i].Equal(otherRDNs[i]) {
return false
}
}
return true
}
// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
// The order of attributes is not significant.
// Case of attribute types is not significant.
func (r *RelativeDN) Equal(other *RelativeDN) bool {
if len(r.Attributes) != len(other.Attributes) {
return false
}
return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
}
func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
for _, attr := range attrs {
found := false
for _, myattr := range r.Attributes {
if myattr.Equal(attr) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
// Case of the attribute type is not significant
func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
}

View File

@@ -31,6 +31,22 @@ func TestSuccessfulDNParsing(t *testing.T) {
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
" CN = Lu\\C4\\8Di\\C4\\87 ": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
` A = 1 , B = 2 `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"A", "1"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"B", "2"}}}}},
` A = 1 + B = 2 `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&ldap.AttributeTypeAndValue{"A", "1"},
&ldap.AttributeTypeAndValue{"B", "2"}}}}},
` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{" A ", " 1 "}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{" B ", " 2 "}}}}},
` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&ldap.AttributeTypeAndValue{" A ", " 1 "},
&ldap.AttributeTypeAndValue{" B ", " 2 "}}}}},
}
for test, answer := range testcases {
@@ -41,6 +57,13 @@ func TestSuccessfulDNParsing(t *testing.T) {
}
if !reflect.DeepEqual(dn, &answer) {
t.Errorf("Parsed DN %s is not equal to the expected structure", test)
t.Logf("Expected:")
for _, rdn := range answer.RDNs {
for _, attribs := range rdn.Attributes {
t.Logf("#%v\n", attribs)
}
}
t.Logf("Actual:")
for _, rdn := range dn.RDNs {
for _, attribs := range rdn.Attributes {
t.Logf("#%v\n", attribs)
@@ -68,3 +91,119 @@ func TestErrorDNParsing(t *testing.T) {
}
}
}
func TestDNEqual(t *testing.T) {
testcases := []struct {
A string
B string
Equal bool
}{
// Exact match
{"", "", true},
{"o=A", "o=A", true},
{"o=A", "o=B", false},
{"o=A,o=B", "o=A,o=B", true},
{"o=A,o=B", "o=A,o=C", false},
{"o=A+o=B", "o=A+o=B", true},
{"o=A+o=B", "o=A+o=C", false},
// Case mismatch in type is ignored
{"o=A", "O=A", true},
{"o=A,o=B", "o=A,O=B", true},
{"o=A+o=B", "o=A+O=B", true},
// Case mismatch in value is significant
{"o=a", "O=A", false},
{"o=a,o=B", "o=A,O=B", false},
{"o=a+o=B", "o=A+O=B", false},
// Multi-valued RDN order mismatch is ignored
{"o=A+o=B", "O=B+o=A", true},
// Number of RDN attributes is significant
{"o=A+o=B", "O=B+o=A+O=B", false},
// Missing values are significant
{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter
// Whitespace tests
// Matching
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe, ou=People, dc=sun.com",
true,
},
// Difference in leading/trailing chars is ignored
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe,ou=People,dc=sun.com",
true,
},
// Difference in values is significant
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe, ou=People, dc=sun.com",
false,
},
}
for i, tc := range testcases {
a, err := ldap.ParseDN(tc.A)
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
b, err := ldap.ParseDN(tc.B)
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
continue
}
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
continue
}
}
}
func TestDNAncestor(t *testing.T) {
testcases := []struct {
A string
B string
Ancestor bool
}{
// Exact match returns false
{"", "", false},
{"o=A", "o=A", false},
{"o=A,o=B", "o=A,o=B", false},
{"o=A+o=B", "o=A+o=B", false},
// Mismatch
{"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false},
// Descendant
{"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true},
}
for i, tc := range testcases {
a, err := ldap.ParseDN(tc.A)
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
b, err := ldap.ParseDN(tc.B)
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
continue
}
}
}

View File

@@ -153,16 +153,47 @@ func addLDAPDescriptions(packet *ber.Packet) (err error) {
func addControlDescriptions(packet *ber.Packet) {
packet.Description = "Controls"
for _, child := range packet.Children {
var value *ber.Packet
controlType := ""
child.Description = "Control"
child.Children[0].Description = "Control Type (" + ControlTypeMap[child.Children[0].Value.(string)] + ")"
value := child.Children[1]
if len(child.Children) == 3 {
child.Children[1].Description = "Criticality"
value = child.Children[2]
}
value.Description = "Control Value"
switch len(child.Children) {
case 0:
// at least one child is required for control type
continue
switch child.Children[0].Value.(string) {
case 1:
// just type, no criticality or value
controlType = child.Children[0].Value.(string)
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
case 2:
controlType = child.Children[0].Value.(string)
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
// Children[1] could be criticality or value (both are optional)
// duck-type on whether this is a boolean
if _, ok := child.Children[1].Value.(bool); ok {
child.Children[1].Description = "Criticality"
} else {
child.Children[1].Description = "Control Value"
value = child.Children[1]
}
case 3:
// criticality and value present
controlType = child.Children[0].Value.(string)
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
child.Children[1].Description = "Criticality"
child.Children[2].Description = "Control Value"
value = child.Children[2]
default:
// more than 3 children is invalid
continue
}
if value == nil {
continue
}
switch controlType {
case ControlTypePaging:
value.Description += " (Paging)"
if value.Value != nil {
@@ -188,18 +219,18 @@ func addControlDescriptions(packet *ber.Packet) {
for _, child := range sequence.Children {
if child.Tag == 0 {
//Warning
child := child.Children[0]
packet := ber.DecodePacket(child.Data.Bytes())
warningPacket := child.Children[0]
packet := ber.DecodePacket(warningPacket.Data.Bytes())
val, ok := packet.Value.(int64)
if ok {
if child.Tag == 0 {
if warningPacket.Tag == 0 {
//timeBeforeExpiration
value.Description += " (TimeBeforeExpiration)"
child.Value = val
} else if child.Tag == 1 {
warningPacket.Value = val
} else if warningPacket.Tag == 1 {
//graceAuthNsRemaining
value.Description += " (GraceAuthNsRemaining)"
child.Value = val
warningPacket.Value = val
}
}
} else if child.Tag == 1 {

View File

@@ -0,0 +1,21 @@
### Issue description
Tell us what should happen and what happens instead
### Example code
```go
If possible, please enter some example code here to reproduce the issue.
```
### Error log
```
If you have an error log, please paste it here.
```
### Configuration
*Driver version (or git SHA):*
*Go version:* run `go version` in your console
*Server version:* E.g. MySQL 5.6, MariaDB 10.0.20
*Server OS:* E.g. Debian 8.1 (Jessie), Windows 10

View File

@@ -0,0 +1,9 @@
### Description
Please explain the changes you made here.
### Checklist
- [ ] Code compiles correctly
- [ ] Created tests which fail without the change (if possible)
- [ ] All tests passing
- [ ] Extended the README / documentation, if necessary
- [ ] Added myself / the copyright holder to the AUTHORS file

View File

@@ -1,6 +1,12 @@
sudo: false
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- tip
before_script:

View File

@@ -14,22 +14,43 @@
Aaron Hopkins <go-sql-driver at die.net>
Arne Hormann <arnehormann at gmail.com>
Carlos Nieto <jose.carlos at menteslibres.net>
Chris Moos <chris at tech9computers.com>
Daniel Nichter <nil at codenode.com>
Daniël van Eeden <git at myname.nl>
DisposaBoy <disposaboy at dby.me>
Frederick Mayle <frederickmayle at gmail.com>
Gustavo Kristic <gkristic at gmail.com>
Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com>
Hirotaka Yamamoto <ymmt2005 at gmail.com>
INADA Naoki <songofacandy at gmail.com>
James Harr <james.harr at gmail.com>
Jian Zhen <zhenjl at gmail.com>
Joshua Prunier <joshua.prunier at gmail.com>
Julien Lefevre <julien.lefevr at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com>
Kamil Dziedzic <kamil at klecza.pl>
Kevin Malachowski <kevin at chowski.com>
Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com>
Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com>
Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org>
Paul Bonser <misterpib at gmail.com>
Runrioter Wung <runrioter at gmail.com>
Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com>
Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc>
Zhenye Xie <xiezhenye at gmail.com>
# Organizations
Barracuda Networks, Inc.
Google Inc.
Stripe Inc.

View File

@@ -1,3 +1,50 @@
## Version 1.3 (2016-12-01)
Changes:
- Go 1.1 is no longer supported
- Use decimals fields in MySQL to format time types (#249)
- Buffer optimizations (#269)
- 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:
- Fixed handling of queries without columns and rows (#255)
- Fixed a panic when SetKeepAlive() failed (#298)
- Handle ERR packets while reading rows (#321)
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
- Actually zero out bytes in handshake response (#378)
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
- Fixed tests with MySQL 5.7.9+ (#380)
- QueryUnescape TLS config names (#397)
- Fixed "broken pipe" error by writing to closed socket (#390)
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
- Fixed parsing of floats into float64 when placeholders are used (#434)
- Fixed DSN tests with Go 1.7+ (#459)
- Handle ERR packets while waiting for EOF (#473)
- Invalidate connection on error while discarding additional results (#513)
- Allow terminating packets of length 0 (#516)
## Version 1.2 (2014-06-03)
Changes:

View File

@@ -4,28 +4,11 @@
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
Please provide the following minimum information:
* Your Go-MySQL-Driver version (or git SHA)
* Your Go version (run `go version` in your console)
* A detailed issue description
* Error Log if present
* If possible, a short example
## Contributing Code
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
Don't forget to add yourself to the AUTHORS file.
### Pull Requests Checklist
Please check the following points before submitting your pull request:
- [x] Code compiles correctly
- [x] Created tests, if possible
- [x] All tests pass
- [x] Extended the README / documentation, if necessary
- [x] Added yourself to the AUTHORS file
### Code Review
Everyone is invited to review and comment on pull requests.

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")
**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)
* [Requirements](#requirements)
@@ -30,7 +26,7 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go
* Connections over TCP/IPv4, TCP/IPv6 or Unix domain sockets
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
* Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB
@@ -38,9 +34,10 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
* Intelligent `LONG DATA` handling in prepared statements
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
* Optional `time.Time` parsing
* Optional placeholder interpolation
## Requirements
* Go 1.1 or higher
* Go 1.2 or higher
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
---------------------------------------
@@ -92,6 +89,8 @@ This has the same effect as an empty DSN string:
```
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
#### Password
Passwords can consist of any character. Escaping is **not** necessary.
@@ -122,6 +121,25 @@ Default: false
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
##### `allowCleartextPasswords`
```
Type: bool
Valid Values: true, false
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.
##### `allowNativePasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowNativePasswords=true` allows the usage of the mysql native password method.
##### `allowOldPasswords`
```
@@ -166,6 +184,33 @@ Default: false
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
##### `columnsWithAlias`
```
Type: bool
Valid Values: true, false
Default: false
```
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
```
SELECT u.id FROM users as u
```
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
##### `interpolateParams`
```
Type: bool
Valid Values: true, false
Default: false
```
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
##### `loc`
@@ -177,8 +222,29 @@ Default: UTC
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
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`
```
Type: bool
Valid Values: true, false
Default: false
```
Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
When `multiStatements` is used, `?` parameters must only be used in the first statement.
##### `parseTime`
@@ -191,6 +257,15 @@ Default: false
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
##### `readTimeout`
```
Type: decimal number
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"*.
##### `strict`
```
@@ -199,10 +274,11 @@ Valid Values: true, 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`
@@ -211,8 +287,7 @@ Type: decimal number
Default: OS default
```
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a 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`
@@ -224,16 +299,33 @@ 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).
##### `writeTimeout`
```
Type: decimal number
Default: 0
```
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### System Variables
All other parameters are interpreted as system variables:
* `autocommit`: `"SET autocommit=<value>"`
* `time_zone`: `"SET time_zone=<value>"`
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
* `param`: `"SET <param>=<value>"`
Any other parameters are interpreted as system variables:
* `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
* `<enum_var>=<value>`: `SET <enum_var>=<value>`
* `<string_var>=%27<value>%27`: `SET <string_var>='<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
```
@@ -248,9 +340,9 @@ root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
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:
@@ -263,11 +355,16 @@ TCP on a remote host, e.g. Amazon RDS:
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
```
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:
```
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
@@ -291,7 +388,7 @@ import "github.com/go-sql-driver/mysql"
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then.
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
@@ -334,9 +431,9 @@ Mozilla summarizes the license scope as follows:
That means:
* You can **use** the **unchanged** source code both in private as also commercial
* You **needn't publish** the source code of your library as long the files licensed under the MPL 2.0 are **unchanged**
* You **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
* You can **use** the **unchanged** source code both in private and commercially
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.

View File

@@ -11,10 +11,13 @@ package mysql
import (
"bytes"
"database/sql"
"database/sql/driver"
"math"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
type TB testing.B
@@ -45,7 +48,11 @@ func initDB(b *testing.B, queries ...string) *sql.DB {
db := tb.checkDB(sql.Open("mysql", dsn))
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
b.Fatalf("Error on %q: %v", query, err)
if w, ok := err.(MySQLWarnings); ok {
b.Logf("warning on %q: %v", query, w)
} else {
b.Fatalf("error on %q: %v", query, err)
}
}
}
return db
@@ -206,3 +213,34 @@ func BenchmarkRoundtripBin(b *testing.B) {
rows.Close()
}
}
func BenchmarkInterpolation(b *testing.B) {
mc := &mysqlConn{
cfg: &Config{
InterpolateParams: true,
Loc: time.UTC,
},
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
buf: newBuffer(nil),
}
args := []driver.Value{
int64(42424242),
float64(math.Pi),
false,
time.Unix(1423411542, 807015000),
[]byte("bytes containing special chars ' \" \a \x00"),
"string containing special chars ' \" \a \x00",
}
q := "SELECT ?, ?, ?, ?, ?, ?"
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := mc.interpolateParams(q, args)
if err != nil {
b.Fatal(err)
}
}
}

View File

@@ -8,7 +8,11 @@
package mysql
import "io"
import (
"io"
"net"
"time"
)
const defaultBufSize = 4096
@@ -18,25 +22,28 @@ const defaultBufSize = 4096
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case.
type buffer struct {
buf []byte
rd io.Reader
idx int
length int
buf []byte
nc net.Conn
idx int
length int
timeout time.Duration
}
func newBuffer(rd io.Reader) buffer {
func newBuffer(nc net.Conn) buffer {
var b [defaultBufSize]byte
return buffer{
buf: b[:],
rd: rd,
nc: nc,
}
}
// fill reads into the buffer until at least _need_ bytes are in it
func (b *buffer) fill(need int) error {
n := b.length
// move existing data to the beginning
if b.length > 0 && b.idx > 0 {
copy(b.buf[0:b.length], b.buf[b.idx:])
if n > 0 && b.idx > 0 {
copy(b.buf[0:n], b.buf[b.idx:])
}
// grow buffer if necessary
@@ -52,19 +59,33 @@ func (b *buffer) fill(need int) error {
b.idx = 0
for {
n, err := b.rd.Read(b.buf[b.length:])
b.length += n
if b.timeout > 0 {
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
return err
}
}
if err == nil {
if b.length < need {
nn, err := b.nc.Read(b.buf[n:])
n += nn
switch err {
case nil:
if n < need {
continue
}
b.length = n
return nil
case io.EOF:
if n >= need {
b.length = n
return nil
}
return io.ErrUnexpectedEOF
default:
return err
}
if b.length >= need && err == io.EOF {
return nil
}
return err
}
}

View File

@@ -8,7 +8,7 @@
package mysql
const defaultCollation byte = 33 // utf8_general_ci
const defaultCollation = "utf8_general_ci"
// A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query:
@@ -234,3 +234,17 @@ var collations = map[string]byte{
"utf8mb4_unicode_520_ci": 246,
"utf8mb4_vietnamese_ci": 247,
}
// A blacklist of collations which is unsafe to interpolate parameters.
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
var unsafeCollations = map[string]bool{
"big5_chinese_ci": true,
"sjis_japanese_ci": true,
"gbk_chinese_ci": true,
"big5_bin": true,
"gb2312_bin": true,
"gbk_bin": true,
"sjis_bin": true,
"cp932_japanese_ci": true,
"cp932_bin": true,
}

View File

@@ -9,10 +9,9 @@
package mysql
import (
"crypto/tls"
"database/sql/driver"
"errors"
"net"
"strconv"
"strings"
"time"
)
@@ -22,34 +21,20 @@ type mysqlConn struct {
netConn net.Conn
affectedRows uint64
insertId uint64
cfg *config
maxPacketAllowed int
cfg *Config
maxAllowedPacket int
maxWriteSize int
writeTimeout time.Duration
flags clientFlag
status statusFlag
sequence uint8
parseTime bool
strict bool
}
type config struct {
user string
passwd string
net string
addr string
dbname string
params map[string]string
loc *time.Location
tls *tls.Config
timeout time.Duration
collation uint8
allowAllFiles bool
allowOldPasswords bool
clientFoundRows bool
}
// Handles parameters set in DSN after the connection is established
func (mc *mysqlConn) handleParams() (err error) {
for param, val := range mc.cfg.params {
for param, val := range mc.cfg.Params {
switch param {
// Charset
case "charset":
@@ -65,27 +50,6 @@ func (mc *mysqlConn) handleParams() (err error) {
return
}
// time.Time parsing
case "parseTime":
var isBool bool
mc.parseTime, isBool = readBool(val)
if !isBool {
return errors.New("Invalid Bool value: " + val)
}
// Strict mode
case "strict":
var isBool bool
mc.strict, isBool = readBool(val)
if !isBool {
return errors.New("Invalid Bool value: " + val)
}
// Compression
case "compress":
err = errors.New("Compression not implemented yet")
return
// System Vars
default:
err = mc.exec("SET " + param + "=" + val + "")
@@ -115,18 +79,27 @@ func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent
if mc.netConn != nil {
err = mc.writeCommandPacket(comQuit)
if err == nil {
err = mc.netConn.Close()
} else {
mc.netConn.Close()
}
mc.cleanup()
return
}
// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {
// Makes cleanup idempotent
if mc.netConn != nil {
if err := mc.netConn.Close(); err != nil {
errLog.Print(err)
}
mc.netConn = nil
}
mc.cfg = nil
mc.buf.rd = nil
return
mc.buf.nc = nil
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
@@ -161,28 +134,156 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
return stmt, err
}
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()
if buf == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return "", driver.ErrBadConn
}
buf = buf[:0]
argPos := 0
for i := 0; i < len(query); i++ {
q := strings.IndexByte(query[i:], '?')
if q == -1 {
buf = append(buf, query[i:]...)
break
}
buf = append(buf, query[i:i+q]...)
i += q
arg := args[argPos]
argPos++
if arg == nil {
buf = append(buf, "NULL"...)
continue
}
switch v := arg.(type) {
case int64:
buf = strconv.AppendInt(buf, v, 10)
case float64:
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
case bool:
if v {
buf = append(buf, '1')
} else {
buf = append(buf, '0')
}
case time.Time:
if v.IsZero() {
buf = append(buf, "'0000-00-00'"...)
} else {
v := v.In(mc.cfg.Loc)
v = v.Add(time.Nanosecond * 500) // To round under microsecond
year := v.Year()
year100 := year / 100
year1 := year % 100
month := v.Month()
day := v.Day()
hour := v.Hour()
minute := v.Minute()
second := v.Second()
micro := v.Nanosecond() / 1000
buf = append(buf, []byte{
'\'',
digits10[year100], digits01[year100],
digits10[year1], digits01[year1],
'-',
digits10[month], digits01[month],
'-',
digits10[day], digits01[day],
' ',
digits10[hour], digits01[hour],
':',
digits10[minute], digits01[minute],
':',
digits10[second], digits01[second],
}...)
if micro != 0 {
micro10000 := micro / 10000
micro100 := micro / 100 % 100
micro1 := micro % 100
buf = append(buf, []byte{
'.',
digits10[micro10000], digits01[micro10000],
digits10[micro100], digits01[micro100],
digits10[micro1], digits01[micro1],
}...)
}
buf = append(buf, '\'')
}
case []byte:
if v == nil {
buf = append(buf, "NULL"...)
} else {
buf = append(buf, "_binary'"...)
if mc.status&statusNoBackslashEscapes == 0 {
buf = escapeBytesBackslash(buf, v)
} else {
buf = escapeBytesQuotes(buf, v)
}
buf = append(buf, '\'')
}
case string:
buf = append(buf, '\'')
if mc.status&statusNoBackslashEscapes == 0 {
buf = escapeStringBackslash(buf, v)
} else {
buf = escapeStringQuotes(buf, v)
}
buf = append(buf, '\'')
default:
return "", driver.ErrSkip
}
if len(buf)+4 > mc.maxAllowedPacket {
return "", driver.ErrSkip
}
}
if argPos != len(args) {
return "", driver.ErrSkip
}
return string(buf), nil
}
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.netConn == nil {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) == 0 { // no args, fastpath
mc.affectedRows = 0
mc.insertId = 0
err := mc.exec(query)
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
return nil, err
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
args = nil
}
mc.affectedRows = 0
mc.insertId = 0
// with args, must use prepared stmt
return nil, driver.ErrSkip
err := mc.exec(query)
if err == nil {
return &mysqlResult{
affectedRows: int64(mc.affectedRows),
insertId: int64(mc.insertId),
}, err
}
return nil, err
}
// Internal function to execute commands
@@ -211,29 +312,38 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) == 0 { // no args, fastpath
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
if resLen > 0 {
// Columns
rows.columns, err = mc.readColumns(resLen)
}
return rows, err
}
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
return nil, err
// try client-side prepare to reduce roundtrip
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
args = nil
}
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc
// with args, must use prepared stmt
return nil, driver.ErrSkip
if resLen == 0 {
// no columns, no more data
return emptyRows{}, nil
}
// Columns
rows.columns, err = mc.readColumns(resLen)
return rows, err
}
}
return nil, err
}
// Gets the value of the given MySQL System Variable
@@ -249,6 +359,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
if err == nil {
rows := new(textRows)
rows.mc = mc
rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
if resLen > 0 {
// Columns

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

@@ -11,7 +11,7 @@ package mysql
const (
minProtocolVersion byte = 10
maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05"
timeFormat = "2006-01-02 15:04:05.999999"
)
// MySQL constants documentation:
@@ -24,6 +24,7 @@ const (
iERR byte = 0xff
)
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
type clientFlag uint32
const (
@@ -45,6 +46,13 @@ const (
clientSecureConn
clientMultiStatements
clientMultiResults
clientPSMultiResults
clientPluginAuth
clientConnectAttrs
clientPluginAuthLenEncClientData
clientCanHandleExpiredPasswords
clientSessionTrack
clientDeprecateEOF
)
const (
@@ -68,7 +76,7 @@ const (
comBinlogDump
comTableDump
comConnectOut
comRegiserSlave
comRegisterSlave
comStmtPrepare
comStmtExecute
comStmtSendLongData
@@ -78,6 +86,7 @@ const (
comStmtFetch
)
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
const (
fieldTypeDecimal byte = iota
fieldTypeTiny
@@ -98,7 +107,8 @@ const (
fieldTypeBit
)
const (
fieldTypeNewDecimal byte = iota + 0xf6
fieldTypeJSON byte = iota + 0xf5
fieldTypeNewDecimal
fieldTypeEnum
fieldTypeSet
fieldTypeTinyBLOB
@@ -130,3 +140,24 @@ const (
flagUnknown3
flagUnknown4
)
// http://dev.mysql.com/doc/internals/en/status-flags.html
type statusFlag uint16
const (
statusInTrans statusFlag = 1 << iota
statusInAutocommit
statusReserved // Not in documentation
statusMoreResultsExists
statusNoGoodIndexUsed
statusNoIndexUsed
statusCursorExists
statusLastRowSent
statusDbDropped
statusNoBackslashEscapes
statusMetadataChanged
statusQueryWasSlow
statusPsOutParams
statusInTransReadonly
statusSessionStateChanged
)

View File

@@ -4,7 +4,7 @@
// 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/.
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
// Package mysql provides a MySQL driver for Go's database/sql package
//
// The driver should be used via the database/sql package:
//
@@ -22,7 +22,7 @@ import (
"net"
)
// This struct is exported to make the driver directly accessible.
// MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package.
type MySQLDriver struct{}
@@ -50,20 +50,22 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
// New mysqlConn
mc := &mysqlConn{
maxPacketAllowed: maxPacketSize,
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
}
mc.cfg, err = parseDSN(dsn)
mc.cfg, err = ParseDSN(dsn)
if err != nil {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
mc.strict = mc.cfg.Strict
// Connect to Server
if dial, ok := dials[mc.cfg.net]; ok {
mc.netConn, err = dial(mc.cfg.addr)
if dial, ok := dials[mc.cfg.Net]; ok {
mc.netConn, err = dial(mc.cfg.Addr)
} else {
nd := net.Dialer{Timeout: mc.cfg.timeout}
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
}
if err != nil {
return nil, err
@@ -72,55 +74,54 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
// Enable TCP Keepalives on TCP connections
if tc, ok := mc.netConn.(*net.TCPConn); ok {
if err := tc.SetKeepAlive(true); err != nil {
mc.Close()
// Don't send COM_QUIT before handshake.
mc.netConn.Close()
mc.netConn = nil
return nil, err
}
}
mc.buf = newBuffer(mc.netConn)
// Set I/O timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
cipher, err := mc.readInitPacket()
if err != nil {
mc.Close()
mc.cleanup()
return nil, err
}
// Send Client Authentication Packet
if err = mc.writeAuthPacket(cipher); err != nil {
mc.Close()
mc.cleanup()
return nil, err
}
// Read Result Packet
err = mc.readResultOK()
if err != nil {
// Retry with old authentication method, if allowed
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
if err = mc.writeOldAuthPacket(cipher); err != nil {
mc.Close()
return nil, err
}
if err = mc.readResultOK(); err != nil {
mc.Close()
return nil, err
}
} else {
// Handle response to auth packet, switch methods if possible
if err = handleAuthResult(mc, cipher); err != nil {
// Authentication failed and MySQL has already closed the connection
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
// Do not send COM_QUIT, just cleanup and return the error.
mc.cleanup()
return nil, err
}
if mc.cfg.MaxAllowedPacket > 0 {
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
} else {
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxAllowedPacket = stringToInt(maxap) - 1
}
// Get max allowed packet size
maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxPacketAllowed = stringToInt(maxap) - 1
if mc.maxPacketAllowed < maxPacketSize {
mc.maxWriteSize = mc.maxPacketAllowed
if mc.maxAllowedPacket < maxPacketSize {
mc.maxWriteSize = mc.maxAllowedPacket
}
// Handle DSN Params
@@ -133,6 +134,50 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil
}
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
// Read Result Packet
cipher, err := mc.readResultOK()
if err == nil {
return nil // auth successful
}
if mc.cfg == nil {
return err // auth failed and retry not possible
}
// Retry auth if configured to do so.
if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
// Retry with old authentication method. Note: there are edge cases
// where this should work but doesn't; this is currently "wontfix":
// 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 {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
// 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/pam-authentication-plugin.html
if err = mc.writeClearAuthPacket(); err != nil {
return err
}
_, err = mc.readResultOK()
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
if err = mc.writeNativeAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
}
return err
}
func init() {
sql.Register("mysql", &MySQLDriver{})
}

File diff suppressed because it is too large Load Diff

548
vendor/github.com/go-sql-driver/mysql/dsn.go generated vendored Normal file
View File

@@ -0,0 +1,548 @@
// 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 (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
)
var (
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
)
// Config is a configuration parsed from a DSN string
type Config struct {
User string // Username
Passwd string // Password (requires User)
Net string // Network type
Addr string // Network address (requires Net)
DBName string // Database name
Params map[string]string // Connection parameters
Collation string // Connection collation
Loc *time.Location // Location for time.Time values
MaxAllowedPacket int // Max packet size allowed
TLSConfig string // TLS configuration name
tls *tls.Config // TLS configuration
Timeout time.Duration // Dial 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
AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string
MultiStatements bool // Allow multiple statements in one query
ParseTime bool // Parse time values to time.Time
Strict bool // Return warnings as errors
}
// FormatDSN formats the given Config into a DSN string which can be passed to
// the driver.
func (cfg *Config) FormatDSN() string {
var buf bytes.Buffer
// [username[:password]@]
if len(cfg.User) > 0 {
buf.WriteString(cfg.User)
if len(cfg.Passwd) > 0 {
buf.WriteByte(':')
buf.WriteString(cfg.Passwd)
}
buf.WriteByte('@')
}
// [protocol[(address)]]
if len(cfg.Net) > 0 {
buf.WriteString(cfg.Net)
if len(cfg.Addr) > 0 {
buf.WriteByte('(')
buf.WriteString(cfg.Addr)
buf.WriteByte(')')
}
}
// /dbname
buf.WriteByte('/')
buf.WriteString(cfg.DBName)
// [?param1=value1&...&paramN=valueN]
hasParam := false
if cfg.AllowAllFiles {
hasParam = true
buf.WriteString("?allowAllFiles=true")
}
if cfg.AllowCleartextPasswords {
if hasParam {
buf.WriteString("&allowCleartextPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowCleartextPasswords=true")
}
}
if cfg.AllowNativePasswords {
if hasParam {
buf.WriteString("&allowNativePasswords=true")
} else {
hasParam = true
buf.WriteString("?allowNativePasswords=true")
}
}
if cfg.AllowOldPasswords {
if hasParam {
buf.WriteString("&allowOldPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowOldPasswords=true")
}
}
if cfg.ClientFoundRows {
if hasParam {
buf.WriteString("&clientFoundRows=true")
} else {
hasParam = true
buf.WriteString("?clientFoundRows=true")
}
}
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
if hasParam {
buf.WriteString("&collation=")
} else {
hasParam = true
buf.WriteString("?collation=")
}
buf.WriteString(col)
}
if cfg.ColumnsWithAlias {
if hasParam {
buf.WriteString("&columnsWithAlias=true")
} else {
hasParam = true
buf.WriteString("?columnsWithAlias=true")
}
}
if cfg.InterpolateParams {
if hasParam {
buf.WriteString("&interpolateParams=true")
} else {
hasParam = true
buf.WriteString("?interpolateParams=true")
}
}
if cfg.Loc != time.UTC && cfg.Loc != nil {
if hasParam {
buf.WriteString("&loc=")
} else {
hasParam = true
buf.WriteString("?loc=")
}
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
}
if cfg.MultiStatements {
if hasParam {
buf.WriteString("&multiStatements=true")
} else {
hasParam = true
buf.WriteString("?multiStatements=true")
}
}
if cfg.ParseTime {
if hasParam {
buf.WriteString("&parseTime=true")
} else {
hasParam = true
buf.WriteString("?parseTime=true")
}
}
if cfg.ReadTimeout > 0 {
if hasParam {
buf.WriteString("&readTimeout=")
} else {
hasParam = true
buf.WriteString("?readTimeout=")
}
buf.WriteString(cfg.ReadTimeout.String())
}
if cfg.Strict {
if hasParam {
buf.WriteString("&strict=true")
} else {
hasParam = true
buf.WriteString("?strict=true")
}
}
if cfg.Timeout > 0 {
if hasParam {
buf.WriteString("&timeout=")
} else {
hasParam = true
buf.WriteString("?timeout=")
}
buf.WriteString(cfg.Timeout.String())
}
if len(cfg.TLSConfig) > 0 {
if hasParam {
buf.WriteString("&tls=")
} else {
hasParam = true
buf.WriteString("?tls=")
}
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
}
if cfg.WriteTimeout > 0 {
if hasParam {
buf.WriteString("&writeTimeout=")
} else {
hasParam = true
buf.WriteString("?writeTimeout=")
}
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
if cfg.Params != nil {
for param, value := range cfg.Params {
if hasParam {
buf.WriteByte('&')
} else {
hasParam = true
buf.WriteByte('?')
}
buf.WriteString(param)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(value))
}
}
return buf.String()
}
// ParseDSN parses the DSN string to a Config
func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values
cfg = &Config{
Loc: time.UTC,
Collation: defaultCollation,
}
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
cfg.Passwd = dsn[k+1 : j]
break
}
}
cfg.User = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return nil, errInvalidDSNUnescaped
}
return nil, errInvalidDSNAddr
}
cfg.Addr = dsn[k+1 : i-1]
break
}
}
cfg.Net = dsn[j+1 : k]
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
return
}
break
}
}
cfg.DBName = dsn[i+1 : j]
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, errInvalidDSNNoSlash
}
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
return nil, errInvalidDSNUnsafeCollation
}
// Set default network if empty
if cfg.Net == "" {
cfg.Net = "tcp"
}
// Set default address if empty
if cfg.Addr == "" {
switch cfg.Net {
case "tcp":
cfg.Addr = "127.0.0.1:3306"
case "unix":
cfg.Addr = "/tmp/mysql.sock"
default:
return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
}
}
return
}
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *Config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.AllowAllFiles, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use cleartext authentication mode (MySQL 5.5.10+)
case "allowCleartextPasswords":
var isBool bool
cfg.AllowCleartextPasswords, isBool = readBool(value)
if !isBool {
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)
case "allowOldPasswords":
var isBool bool
cfg.AllowOldPasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.ClientFoundRows, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Collation
case "collation":
cfg.Collation = value
break
case "columnsWithAlias":
var isBool bool
cfg.ColumnsWithAlias, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Compression
case "compress":
return errors.New("compression not implemented yet")
// Enable client side placeholder substitution
case "interpolateParams":
var isBool bool
cfg.InterpolateParams, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.Loc, err = time.LoadLocation(value)
if err != nil {
return
}
// multiple statements in one query
case "multiStatements":
var isBool bool
cfg.MultiStatements, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// time.Time parsing
case "parseTime":
var isBool bool
cfg.ParseTime, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// I/O read Timeout
case "readTimeout":
cfg.ReadTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
// Strict mode
case "strict":
var isBool bool
cfg.Strict, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Dial Timeout
case "timeout":
cfg.Timeout, err = time.ParseDuration(value)
if err != nil {
return
}
// TLS-Encryption
case "tls":
boolValue, isBool := readBool(value)
if isBool {
if boolValue {
cfg.TLSConfig = "true"
cfg.tls = &tls.Config{}
} else {
cfg.TLSConfig = "false"
}
} else if vl := strings.ToLower(value); vl == "skip-verify" {
cfg.TLSConfig = vl
cfg.tls = &tls.Config{InsecureSkipVerify: true}
} else {
name, err := url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("invalid value for TLS config name: %v", err)
}
if tlsConfig, ok := tlsConfigRegister[name]; ok {
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil {
tlsConfig.ServerName = host
}
}
cfg.TLSConfig = name
cfg.tls = tlsConfig
} else {
return errors.New("invalid value / unknown config name: " + name)
}
}
// I/O write Timeout
case "writeTimeout":
cfg.WriteTimeout, err = time.ParseDuration(value)
if err != nil {
return
}
case "maxAllowedPacket":
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
if err != nil {
return
}
default:
// lazy init
if cfg.Params == nil {
cfg.Params = make(map[string]string)
}
if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
return
}
}
}
return
}

231
vendor/github.com/go-sql-driver/mysql/dsn_test.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
// 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 (
"crypto/tls"
"fmt"
"net/url"
"reflect"
"testing"
"time"
)
var testDSNs = []struct {
in string
out *Config
}{{
"username:password@protocol(address)/dbname?param=value",
&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",
&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},
}, {
"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
&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},
}, {
"user@unix(/path/to/socket)/dbname?charset=utf8",
&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:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
&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) {
for i, tst := range testDSNs {
cfg, err := ParseDSN(tst.in)
if err != nil {
t.Error(err.Error())
}
// pointer not static
cfg.tls = nil
if !reflect.DeepEqual(cfg, tst.out) {
t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out)
}
}
}
func TestDSNParserInvalid(t *testing.T) {
var invalidDSNs = []string{
"@net(addr/", // no closing brace
"@tcp(/", // no closing brace
"tcp(/", // no closing brace
"(/", // no closing brace
"net(addr)//", // unescaped
"User:pass@tcp(1.2.3.4:3306)", // no trailing slash
//"/dbname?arg=/some/unescaped/path",
}
for i, tst := range invalidDSNs {
if _, err := ParseDSN(tst); err == nil {
t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
}
}
}
func TestDSNReformat(t *testing.T) {
for i, tst := range testDSNs {
dsn1 := tst.in
cfg1, err := ParseDSN(dsn1)
if err != nil {
t.Error(err.Error())
continue
}
cfg1.tls = nil // pointer not static
res1 := fmt.Sprintf("%+v", cfg1)
dsn2 := cfg1.FormatDSN()
cfg2, err := ParseDSN(dsn2)
if err != nil {
t.Error(err.Error())
continue
}
cfg2.tls = nil // pointer not static
res2 := fmt.Sprintf("%+v", cfg2)
if res1 != res2 {
t.Errorf("%d. %q does not match %q", i, res2, res1)
}
}
}
func TestDSNWithCustomTLS(t *testing.T) {
baseDSN := "User:password@tcp(localhost:5555)/dbname?tls="
tlsCfg := tls.Config{}
RegisterTLSConfig("utils_test", &tlsCfg)
// Custom TLS is missing
tst := baseDSN + "invalid_tls"
cfg, err := ParseDSN(tst)
if err == nil {
t.Errorf("invalid custom TLS in DSN (%s) but did not error. Got config: %#v", tst, cfg)
}
tst = baseDSN + "utils_test"
// Custom TLS with a server name
name := "foohost"
tlsCfg.ServerName = name
cfg, err = ParseDSN(tst)
if err != nil {
t.Error(err.Error())
} else if cfg.tls.ServerName != name {
t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
}
// Custom TLS without a server name
name = "localhost"
tlsCfg.ServerName = ""
cfg, err = ParseDSN(tst)
if err != nil {
t.Error(err.Error())
} else if cfg.tls.ServerName != name {
t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
}
DeregisterTLSConfig("utils_test")
}
func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
const configKey = "&%!:"
dsn := "User:password@tcp(localhost:5555)/dbname?tls=" + url.QueryEscape(configKey)
name := "foohost"
tlsCfg := tls.Config{ServerName: name}
RegisterTLSConfig(configKey, &tlsCfg)
cfg, err := ParseDSN(dsn)
if err != nil {
t.Error(err.Error())
} else if cfg.tls.ServerName != name {
t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn)
}
}
func TestDSNUnsafeCollation(t *testing.T) {
_, err := ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=true")
if err != errInvalidDSNUnsafeCollation {
t.Errorf("expected %v, got %v", errInvalidDSNUnsafeCollation, err)
}
_, err = ParseDSN("/dbname?collation=gbk_chinese_ci&interpolateParams=false")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
_, err = ParseDSN("/dbname?collation=gbk_chinese_ci")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
_, err = ParseDSN("/dbname?collation=ascii_bin&interpolateParams=true")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
_, err = ParseDSN("/dbname?collation=latin1_german1_ci&interpolateParams=true")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
_, err = ParseDSN("/dbname?collation=utf8_general_ci&interpolateParams=true")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
_, err = ParseDSN("/dbname?collation=utf8mb4_general_ci&interpolateParams=true")
if err != nil {
t.Errorf("expected %v, got %v", nil, err)
}
}
func BenchmarkParseDSN(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tst := range testDSNs {
if _, err := ParseDSN(tst.in); err != nil {
b.Error(err.Error())
}
}
}
}

View File

@@ -19,18 +19,21 @@ 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 encryption requested but server does not support TLS")
ErrOldPassword = errors.New("This server only supports the insecure 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")
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. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
ErrBusyBuffer = errors.New("Busy buffer")
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")
)
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
// Logger is used to log critical error messages.
type Logger interface {

View File

@@ -13,11 +13,14 @@ import (
"io"
"os"
"strings"
"sync"
)
var (
fileRegister map[string]bool
readerRegister map[string]func() io.Reader
fileRegister map[string]bool
fileRegisterLock sync.RWMutex
readerRegister map[string]func() io.Reader
readerRegisterLock sync.RWMutex
)
// RegisterLocalFile adds the given file to the file whitelist,
@@ -32,17 +35,21 @@ var (
// ...
//
func RegisterLocalFile(filePath string) {
fileRegisterLock.Lock()
// lazy map init
if fileRegister == nil {
fileRegister = make(map[string]bool)
}
fileRegister[strings.Trim(filePath, `"`)] = true
fileRegisterLock.Unlock()
}
// DeregisterLocalFile removes the given filepath from the whitelist.
func DeregisterLocalFile(filePath string) {
fileRegisterLock.Lock()
delete(fileRegister, strings.Trim(filePath, `"`))
fileRegisterLock.Unlock()
}
// RegisterReaderHandler registers a handler function which is used
@@ -61,18 +68,22 @@ func DeregisterLocalFile(filePath string) {
// ...
//
func RegisterReaderHandler(name string, handler func() io.Reader) {
readerRegisterLock.Lock()
// lazy map init
if readerRegister == nil {
readerRegister = make(map[string]func() io.Reader)
}
readerRegister[name] = handler
readerRegisterLock.Unlock()
}
// DeregisterReaderHandler removes the ReaderHandler function with
// the given name from the registry.
func DeregisterReaderHandler(name string) {
readerRegisterLock.Lock()
delete(readerRegister, name)
readerRegisterLock.Unlock()
}
func deferredClose(err *error, closer io.Closer) {
@@ -85,14 +96,22 @@ func deferredClose(err *error, closer io.Closer) {
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
var rdr io.Reader
var data []byte
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
if mc.maxWriteSize < packetSize {
packetSize = mc.maxWriteSize
}
if strings.HasPrefix(name, "Reader::") { // io.Reader
name = name[8:]
if handler, inMap := readerRegister[name]; inMap {
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
// The server might return an an absolute path. See issue #355.
name = name[idx+8:]
readerRegisterLock.RLock()
handler, inMap := readerRegister[name]
readerRegisterLock.RUnlock()
if inMap {
rdr = handler()
if rdr != nil {
data = make([]byte, 4+mc.maxWriteSize)
if cl, ok := rdr.(io.Closer); ok {
defer deferredClose(&err, cl)
}
@@ -104,7 +123,10 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
}
} else { // File
name = strings.Trim(name, `"`)
if mc.cfg.allowAllFiles || fileRegister[name] {
fileRegisterLock.RLock()
fr := fileRegister[name]
fileRegisterLock.RUnlock()
if mc.cfg.AllowAllFiles || fr {
var file *os.File
var fi os.FileInfo
@@ -114,22 +136,19 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
// get file size
if fi, err = file.Stat(); err == nil {
rdr = file
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
data = make([]byte, 4+fileSize)
} else if fileSize <= mc.maxPacketAllowed {
data = make([]byte, 4+mc.maxWriteSize)
} else {
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
if fileSize := int(fi.Size()); fileSize < packetSize {
packetSize = fileSize
}
}
}
} else {
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
err = fmt.Errorf("local file '%s' is not registered", name)
}
}
// send content packets
if err == nil {
data := make([]byte, 4+packetSize)
var n int
for err == nil {
n, err = rdr.Read(data[4:])
@@ -154,9 +173,10 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
// read OK packet
if err == nil {
return mc.readResultOK()
} else {
mc.readPacket()
_, err = mc.readResultOK()
return err
}
mc.readPacket()
return err
}

View File

@@ -13,6 +13,7 @@ import (
"crypto/tls"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
@@ -24,9 +25,9 @@ import (
// Read packet to buffer 'data'
func (mc *mysqlConn) readPacket() ([]byte, error) {
var payload []byte
var prevData []byte
for {
// Read packet header
// read packet header
data, err := mc.buf.readNext(4)
if err != nil {
errLog.Print(err)
@@ -34,26 +35,32 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
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)
if pktLen < 1 {
errLog.Print(ErrMalformPkt)
mc.Close()
return nil, driver.ErrBadConn
}
// Check Packet Sync [8 bit]
// check packet sync [8 bit]
if data[3] != mc.sequence {
if data[3] > mc.sequence {
return nil, ErrPktSyncMul
} else {
return nil, ErrPktSync
}
return nil, ErrPktSync
}
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)
if err != nil {
errLog.Print(err)
@@ -61,18 +68,17 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
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
if isLastPacket && payload == nil {
return data, nil
return append(prevData, data...), nil
}
payload = append(payload, data...)
if isLastPacket {
return payload, nil
}
prevData = append(prevData, data...)
}
}
@@ -80,7 +86,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
func (mc *mysqlConn) writePacket(data []byte) error {
pktLen := len(data) - 4
if pktLen > mc.maxPacketAllowed {
if pktLen > mc.maxAllowedPacket {
return ErrPktTooLarge
}
@@ -100,6 +106,12 @@ func (mc *mysqlConn) writePacket(data []byte) error {
data[3] = mc.sequence
// Write packet
if mc.writeTimeout > 0 {
if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
return err
}
}
n, err := mc.netConn.Write(data[:4+size])
if err == nil && n == 4+size {
mc.sequence++
@@ -140,7 +152,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
// protocol version [1 byte]
if data[0] < minProtocolVersion {
return nil, fmt.Errorf(
"Unsupported MySQL Protocol Version %d. Protocol Version %d or higher is required",
"unsupported protocol version %d. Version %d or higher is required",
data[0],
minProtocolVersion,
)
@@ -196,7 +208,11 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
// return
//}
//return ErrMalformPkt
return cipher, nil
// make a memory safe copy of the cipher slice
var b [20]byte
copy(b[:], cipher)
return b[:], nil
}
// make a memory safe copy of the cipher slice
@@ -214,9 +230,11 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
clientLongPassword |
clientTransactions |
clientLocalFiles |
clientPluginAuth |
clientMultiResults |
mc.flags&clientLongFlag
if mc.cfg.clientFoundRows {
if mc.cfg.ClientFoundRows {
clientFlags |= clientFoundRows
}
@@ -225,13 +243,17 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
clientFlags |= clientSSL
}
// User Password
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.passwd))
if mc.cfg.MultiStatements {
clientFlags |= clientMultiStatements
}
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.user) + 1 + 1 + len(scrambleBuff)
// User Password
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
// To specify a db name
if n := len(mc.cfg.dbname); n > 0 {
if n := len(mc.cfg.DBName); n > 0 {
clientFlags |= clientConnectWithDB
pktLen += n + 1
}
@@ -257,7 +279,14 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
data[11] = 0x00
// Charset [1 byte]
data[12] = mc.cfg.collation
var found bool
data[12], found = collations[mc.cfg.Collation]
if !found {
// Note possibility for false negatives:
// could be triggered although the collation is valid if the
// collations map does not contain entries the server supports.
return errors.New("unknown collation")
}
// SSL Connection Request Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
@@ -273,15 +302,18 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
return err
}
mc.netConn = tlsConn
mc.buf.rd = tlsConn
mc.buf.nc = tlsConn
}
// Filler [23 bytes] (all 0x00)
pos := 13 + 23
pos := 13
for ; pos < 13+23; pos++ {
data[pos] = 0
}
// User [null terminated string]
if len(mc.cfg.user) > 0 {
pos += copy(data[pos:], mc.cfg.user)
if len(mc.cfg.User) > 0 {
pos += copy(data[pos:], mc.cfg.User)
}
data[pos] = 0x00
pos++
@@ -291,11 +323,16 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
pos += 1 + copy(data[pos+1:], scrambleBuff)
// Databasename [null terminated string]
if len(mc.cfg.dbname) > 0 {
pos += copy(data[pos:], mc.cfg.dbname)
if len(mc.cfg.DBName) > 0 {
pos += copy(data[pos:], mc.cfg.DBName)
data[pos] = 0x00
pos++
}
// Assume native client during response
pos += copy(data[pos:], "mysql_native_password")
data[pos] = 0x00
// Send Auth packet
return mc.writePacket(data)
}
@@ -304,9 +341,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
// User password
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.passwd))
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
// Calculate the packet lenght and add a tailing 0
// Calculate the packet length and add a tailing 0
pktLen := len(scrambleBuff) + 1
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil {
@@ -322,6 +359,45 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
return mc.writePacket(data)
}
// Client clear text authentication packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeClearAuthPacket() error {
// Calculate the packet length and add a tailing 0
pktLen := len(mc.cfg.Passwd) + 1
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 clear password [null terminated string]
copy(data[4:], mc.cfg.Passwd)
data[4+pktLen-1] = 0x00
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 *
******************************************************************************/
@@ -395,24 +471,43 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
******************************************************************************/
// 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()
if err == nil {
// packet indicator
switch data[0] {
case iOK:
return mc.handleOkPacket(data)
return nil, mc.handleOkPacket(data)
case iEOF:
// someone is using old_passwords
return ErrOldPassword
if len(data) > 1 {
pluginEndIndex := bytes.IndexByte(data, 0x00)
plugin := string(data[1:pluginEndIndex])
cipher := data[pluginEndIndex+1 : len(data)-1]
if plugin == "mysql_old_password" {
// using old_passwords
return cipher, ErrOldPassword
} else if plugin == "mysql_clear_password" {
// using clear text password
return cipher, ErrCleartextPassword
} else if plugin == "mysql_native_password" {
// using mysql default authentication method
return cipher, ErrNativePassword
} else {
return cipher, ErrUnknownPlugin
}
} else {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, ErrOldPassword
}
default: // Error otherwise
return mc.handleErrorPacket(data)
return nil, mc.handleErrorPacket(data)
}
}
return err
return nil, err
}
// Result Set Header Packet
@@ -470,6 +565,10 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
}
}
func readStatus(b []byte) statusFlag {
return statusFlag(b[0]) | statusFlag(b[1])<<8
}
// Ok Packet
// http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet
func (mc *mysqlConn) handleOkPacket(data []byte) error {
@@ -484,17 +583,21 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
mc.insertId, _, m = readLengthEncodedInteger(data[1+n:])
// server_status [2 bytes]
mc.status = readStatus(data[1+n+m : 1+n+m+2])
if err := mc.discardResults(); err != nil {
return err
}
// warning count [2 bytes]
if !mc.strict {
return nil
} else {
pos := 1 + n + m + 2
if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
return mc.getWarnings()
}
return nil
}
pos := 1 + n + m + 2
if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
return mc.getWarnings()
}
return nil
}
// Read Packets as Field Packets until EOF-Packet or an Error appears
@@ -513,7 +616,7 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
if i == count {
return columns, nil
}
return nil, fmt.Errorf("ColumnsCount mismatch n:%d len:%d", count, len(columns))
return nil, fmt.Errorf("column count mismatch n:%d len:%d", count, len(columns))
}
// Catalog
@@ -530,11 +633,20 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
pos += n
// Table [len coded string]
n, err = skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
if mc.cfg.ColumnsWithAlias {
tableName, _, n, err := readLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
columns[i].tableName = string(tableName)
} else {
n, err = skipLengthEncodedString(data[pos:])
if err != nil {
return nil, err
}
pos += n
}
pos += n
// Original table [len coded string]
n, err = skipLengthEncodedString(data[pos:])
@@ -557,20 +669,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
return nil, err
}
// Filler [1 byte]
// Charset [16 bit uint]
// Length [32 bit uint]
// Filler [uint8]
// Charset [charset, collation uint8]
// Length [uint32]
pos += n + 1 + 2 + 4
// Field type [byte]
// Field type [uint8]
columns[i].fieldType = data[pos]
pos++
// Flags [16 bit uint]
// Flags [uint16]
columns[i].flags = fieldFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
//pos += 2
pos += 2
// Decimals [8 bit uint]
// Decimals [uint8]
columns[i].decimals = data[pos]
//pos++
// Default value [len coded binary]
@@ -592,7 +705,21 @@ func (rows *textRows) readRow(dest []driver.Value) error {
// EOF Packet
if data[0] == iEOF && len(data) == 5 {
return io.EOF
// server_status [2 bytes]
rows.mc.status = readStatus(data[3:])
err = rows.mc.discardResults()
if err == nil {
err = io.EOF
} else {
// connection unusable
rows.mc.Close()
}
rows.mc = nil
return err
}
if data[0] == iERR {
rows.mc = nil
return mc.handleErrorPacket(data)
}
// RowSet Packet
@@ -614,7 +741,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime(
string(dest[i].([]byte)),
mc.cfg.loc,
mc.cfg.Loc,
)
if err == nil {
continue
@@ -639,12 +766,19 @@ func (rows *textRows) readRow(dest []driver.Value) error {
func (mc *mysqlConn) readUntilEOF() error {
for {
data, err := mc.readPacket()
// No Err and no EOF Packet
if err == nil && data[0] != iEOF {
continue
if err != nil {
return err
}
switch data[0] {
case iERR:
return mc.handleErrorPacket(data)
case iEOF:
if len(data) == 5 {
mc.status = readStatus(data[3:])
}
return nil
}
return err // Err or EOF
}
}
@@ -676,20 +810,20 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
// Warning count [16 bit uint]
if !stmt.mc.strict {
return columnCount, nil
} else {
// Check for warnings count > 0, only available in MySQL > 4.1
if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
return columnCount, stmt.mc.getWarnings()
}
return columnCount, nil
}
// Check for warnings count > 0, only available in MySQL > 4.1
if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
return columnCount, stmt.mc.getWarnings()
}
return columnCount, nil
}
return 0, err
}
// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html
func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
maxLen := stmt.mc.maxPacketAllowed - 1
maxLen := stmt.mc.maxAllowedPacket - 1
pktLen := maxLen
// After the header (bytes 0-3) follows before the data:
@@ -744,7 +878,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
if len(args) != stmt.paramCount {
return fmt.Errorf(
"Arguments count mismatch (Got: %d Has: %d)",
"argument count mismatch (got: %d; has: %d)",
len(args),
stmt.paramCount,
)
@@ -880,7 +1014,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramTypes[i+i] = fieldTypeString
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,
uint64(len(v)),
)
@@ -902,7 +1036,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramTypes[i+i] = fieldTypeString
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,
uint64(len(v)),
)
@@ -921,7 +1055,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
if v.IsZero() {
val = []byte("0000-00-00")
} else {
val = []byte(v.In(mc.cfg.loc).Format(timeFormat))
val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
}
paramValues = appendLengthEncodedInteger(paramValues,
@@ -930,7 +1064,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramValues = append(paramValues, val...)
default:
return fmt.Errorf("Can't convert type: %T", arg)
return fmt.Errorf("can not convert type: %T", arg)
}
}
@@ -948,6 +1082,28 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
return mc.writePacket(data)
}
func (mc *mysqlConn) discardResults() error {
for mc.status&statusMoreResultsExists != 0 {
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return err
}
if resLen > 0 {
// columns
if err := mc.readUntilEOF(); err != nil {
return err
}
// rows
if err := mc.readUntilEOF(); err != nil {
return err
}
} else {
mc.status &^= statusMoreResultsExists
}
}
return nil
}
// http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
func (rows *binaryRows) readRow(dest []driver.Value) error {
data, err := rows.mc.readPacket()
@@ -959,8 +1115,18 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
if data[0] != iOK {
// EOF Packet
if data[0] == iEOF && len(data) == 5 {
return io.EOF
rows.mc.status = readStatus(data[3:])
err = rows.mc.discardResults()
if err == nil {
err = io.EOF
} else {
// connection unusable
rows.mc.Close()
}
rows.mc = nil
return err
}
rows.mc = nil
// Error otherwise
return rows.mc.handleErrorPacket(data)
@@ -1027,7 +1193,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
continue
case fieldTypeFloat:
dest[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
pos += 4
continue
@@ -1040,7 +1206,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
fieldTypeVarString, fieldTypeString, fieldTypeGeometry:
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON:
var isNull bool
var n int
dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
@@ -1055,88 +1221,53 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
}
return err
// Date YYYY-MM-DD
case fieldTypeDate, fieldTypeNewDate:
case
fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
fieldTypeTime, // Time [-][H]HH:MM:SS[.fractal]
fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
num, isNull, n := readLengthEncodedInteger(data[pos:])
pos += n
if isNull {
switch {
case isNull:
dest[i] = nil
continue
}
if rows.mc.parseTime {
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
} else {
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], false)
}
if err == nil {
pos += int(num)
continue
} else {
return err
}
// Time [-][H]HH:MM:SS[.fractal]
case fieldTypeTime:
num, isNull, n := readLengthEncodedInteger(data[pos:])
pos += n
if num == 0 {
if isNull {
dest[i] = nil
continue
} else {
dest[i] = []byte("00:00:00")
continue
case rows.columns[i].fieldType == fieldTypeTime:
// database/sql does not support an equivalent to TIME, return a string
var dstlen uint8
switch decimals := rows.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 8
case 1, 2, 3, 4, 5, 6:
dstlen = 8 + 1 + decimals
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
rows.columns[i].decimals,
)
}
}
var sign string
if data[pos] == 1 {
sign = "-"
}
switch num {
case 8:
dest[i] = []byte(fmt.Sprintf(
sign+"%02d:%02d:%02d",
uint16(data[pos+1])*24+uint16(data[pos+5]),
data[pos+6],
data[pos+7],
))
pos += 8
continue
case 12:
dest[i] = []byte(fmt.Sprintf(
sign+"%02d:%02d:%02d.%06d",
uint16(data[pos+1])*24+uint16(data[pos+5]),
data[pos+6],
data[pos+7],
binary.LittleEndian.Uint32(data[pos+8:pos+12]),
))
pos += 12
continue
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
case rows.mc.parseTime:
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
default:
return fmt.Errorf("Invalid TIME-packet length %d", num)
}
// Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
case fieldTypeTimestamp, fieldTypeDateTime:
num, isNull, n := readLengthEncodedInteger(data[pos:])
pos += n
if isNull {
dest[i] = nil
continue
}
if rows.mc.parseTime {
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
} else {
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], true)
var dstlen uint8
if rows.columns[i].fieldType == fieldTypeDate {
dstlen = 10
} else {
switch decimals := rows.columns[i].decimals; decimals {
case 0x00, 0x1f:
dstlen = 19
case 1, 2, 3, 4, 5, 6:
dstlen = 19 + 1 + decimals
default:
return fmt.Errorf(
"protocol error, illegal decimals value %d",
rows.columns[i].decimals,
)
}
}
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
}
if err == nil {
@@ -1148,7 +1279,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// Please report if this happens!
default:
return fmt.Errorf("Unknown FieldType %d", rows.columns[i].fieldType)
return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
}
}

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

@@ -14,9 +14,11 @@ import (
)
type mysqlField struct {
fieldType byte
flags fieldFlag
tableName string
name string
flags fieldFlag
fieldType byte
decimals byte
}
type mysqlRows struct {
@@ -32,10 +34,22 @@ type textRows struct {
mysqlRows
}
type emptyRows struct{}
func (rows *mysqlRows) Columns() []string {
columns := make([]string, len(rows.columns))
for i := range columns {
columns[i] = rows.columns[i].name
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
for i := range columns {
if tableName := rows.columns[i].tableName; len(tableName) > 0 {
columns[i] = tableName + "." + rows.columns[i].name
} else {
columns[i] = rows.columns[i].name
}
}
} else {
for i := range columns {
columns[i] = rows.columns[i].name
}
}
return columns
}
@@ -51,6 +65,12 @@ func (rows *mysqlRows) Close() error {
// Remove unread packets from stream
err := mc.readUntilEOF()
if err == nil {
if err = mc.discardResults(); err != nil {
return err
}
}
rows.mc = nil
return err
}
@@ -62,10 +82,7 @@ func (rows *binaryRows) Next(dest []driver.Value) error {
}
// Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF {
return err
}
rows.mc = nil
return rows.readRow(dest)
}
return io.EOF
}
@@ -77,10 +94,19 @@ func (rows *textRows) Next(dest []driver.Value) error {
}
// Fetch next row from stream
if err := rows.readRow(dest); err != io.EOF {
return err
}
rows.mc = nil
return rows.readRow(dest)
}
return io.EOF
}
func (rows emptyRows) Columns() []string {
return nil
}
func (rows emptyRows) Close() error {
return nil
}
func (rows emptyRows) Next(dest []driver.Value) error {
return io.EOF
}

View File

@@ -10,6 +10,9 @@ package mysql
import (
"database/sql/driver"
"fmt"
"reflect"
"strconv"
)
type mysqlStmt struct {
@@ -21,7 +24,10 @@ type mysqlStmt struct {
func (stmt *mysqlStmt) Close() error {
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
}
@@ -34,6 +40,10 @@ func (stmt *mysqlStmt) NumInput() int {
return stmt.paramCount
}
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
return converter{}
}
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.netConn == nil {
errLog.Print(ErrInvalidConn)
@@ -94,9 +104,9 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
}
rows := new(binaryRows)
rows.mc = mc
if resLen > 0 {
rows.mc = mc
// Columns
// If not cached, read them and cache them
if stmt.columns == nil {
@@ -110,3 +120,34 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
return rows, err
}
type converter struct{}
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(v) {
return v, nil
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Ptr:
// indirect pointers
if rv.IsNil() {
return nil, nil
}
return c.ConvertValue(rv.Elem().Interface())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
return int64(rv.Uint()), nil
case reflect.Uint64:
u64 := rv.Uint()
if u64 >= 1<<63 {
return strconv.FormatUint(u64, 10), nil
}
return int64(u64), nil
case reflect.Float32, reflect.Float64:
return rv.Float(), nil
}
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}

View File

@@ -13,26 +13,16 @@ import (
"crypto/tls"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"net/url"
"strings"
"time"
)
var (
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
)
func init() {
tlsConfigRegister = make(map[string]*tls.Config)
}
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
// Use the key as a value in the DSN where tls=value.
//
@@ -58,7 +48,11 @@ func init() {
//
func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
return fmt.Errorf("Key '%s' is reserved", key)
return fmt.Errorf("key '%s' is reserved", key)
}
if tlsConfigRegister == nil {
tlsConfigRegister = make(map[string]*tls.Config)
}
tlsConfigRegister[key] = config
@@ -67,202 +61,9 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
delete(tlsConfigRegister, key)
}
// parseDSN parses the DSN string to a config
func parseDSN(dsn string) (cfg *config, err error) {
// New config with some default values
cfg = &config{
loc: time.UTC,
collation: defaultCollation,
if tlsConfigRegister != nil {
delete(tlsConfigRegister, key)
}
// TODO: use strings.IndexByte when we can depend on Go 1.2
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
cfg.passwd = dsn[k+1 : j]
break
}
}
cfg.user = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return nil, errInvalidDSNUnescaped
}
return nil, errInvalidDSNAddr
}
cfg.addr = dsn[k+1 : i-1]
break
}
}
cfg.net = dsn[j+1 : k]
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
return
}
break
}
}
cfg.dbname = dsn[i+1 : j]
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, errInvalidDSNNoSlash
}
// Set default network if empty
if cfg.net == "" {
cfg.net = "tcp"
}
// Set default address if empty
if cfg.addr == "" {
switch cfg.net {
case "tcp":
cfg.addr = "127.0.0.1:3306"
case "unix":
cfg.addr = "/tmp/mysql.sock"
default:
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
}
}
return
}
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Disable INFILE whitelist / enable all files
case "allowAllFiles":
var isBool bool
cfg.allowAllFiles, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords":
var isBool bool
cfg.allowOldPasswords, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Switch "rowsAffected" mode
case "clientFoundRows":
var isBool bool
cfg.clientFoundRows, isBool = readBool(value)
if !isBool {
return fmt.Errorf("Invalid Bool value: %s", value)
}
// Collation
case "collation":
collation, ok := collations[value]
if !ok {
// Note possibility for false negatives:
// could be triggered although the collation is valid if the
// collations map does not contain entries the server supports.
err = errors.New("unknown collation")
return
}
cfg.collation = collation
break
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.loc, err = time.LoadLocation(value)
if err != nil {
return
}
// Dial Timeout
case "timeout":
cfg.timeout, err = time.ParseDuration(value)
if err != nil {
return
}
// TLS-Encryption
case "tls":
boolValue, isBool := readBool(value)
if isBool {
if boolValue {
cfg.tls = &tls.Config{}
}
} else {
if strings.ToLower(value) == "skip-verify" {
cfg.tls = &tls.Config{InsecureSkipVerify: true}
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
cfg.tls = tlsConfig
} else {
return fmt.Errorf("Invalid value / unknown config name: %s", value)
}
}
default:
// lazy init
if cfg.params == nil {
cfg.params = make(map[string]string)
}
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
return
}
}
}
return
}
// Returns the bool value of the input.
@@ -451,19 +252,15 @@ func (nt NullTime) Value() (driver.Value, error) {
}
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
base := "0000-00-00 00:00:00.0000000"
switch len(str) {
case 10: // YYYY-MM-DD
if str == "0000-00-00" {
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
if str == base[:len(str)] {
return
}
t, err = time.Parse(timeFormat[:10], str)
case 19: // YYYY-MM-DD HH:MM:SS
if str == "0000-00-00 00:00:00" {
return
}
t, err = time.Parse(timeFormat, str)
t, err = time.Parse(timeFormat[:len(str)], str)
default:
err = fmt.Errorf("Invalid Time-String: %s", str)
err = fmt.Errorf("invalid time string: %s", str)
return
}
@@ -512,87 +309,151 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
loc,
), nil
}
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
}
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
// if the DATE or DATETIME has the zero value.
// It must never be changed.
// The current behavior depends on database/sql copying the result.
var zeroDateTime = []byte("0000-00-00 00:00:00")
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) {
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
if withTime {
return zeroDateTime, nil
if justTime {
return zeroDateTime[11 : 11+length], nil
}
return zeroDateTime[:10], nil
return zeroDateTime[:length], nil
}
var dst []byte
if withTime {
if len(src) == 11 {
dst = []byte("0000-00-00 00:00:00.000000")
var dst []byte // return value
var pt, p1, p2, p3 byte // current digit pair
var zOffs byte // offset of value in zeroDateTime
if justTime {
switch length {
case
8, // time (can be up to 10 when negative and 100+ hours)
10, 11, 12, 13, 14, 15: // time with fractional seconds
default:
return nil, fmt.Errorf("illegal TIME length %d", length)
}
switch len(src) {
case 8, 12:
default:
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
if src[1] != 0 {
hour := uint16(src[1])*24 + uint16(src[5])
pt = byte(hour / 100)
p1 = byte(hour - 100*uint16(pt))
dst = append(dst, digits01[pt])
} else {
dst = []byte("0000-00-00 00:00:00")
p1 = src[5]
}
zOffs = 11
src = src[6:]
} else {
dst = []byte("0000-00-00")
}
switch len(src) {
case 11:
microsecs := binary.LittleEndian.Uint32(src[7:11])
tmp32 := microsecs / 10
dst[25] += byte(microsecs - 10*tmp32)
tmp32, microsecs = tmp32/10, tmp32
dst[24] += byte(microsecs - 10*tmp32)
tmp32, microsecs = tmp32/10, tmp32
dst[23] += byte(microsecs - 10*tmp32)
tmp32, microsecs = tmp32/10, tmp32
dst[22] += byte(microsecs - 10*tmp32)
tmp32, microsecs = tmp32/10, tmp32
dst[21] += byte(microsecs - 10*tmp32)
dst[20] += byte(microsecs / 10)
fallthrough
case 7:
second := src[6]
tmp := second / 10
dst[18] += second - 10*tmp
dst[17] += tmp
minute := src[5]
tmp = minute / 10
dst[15] += minute - 10*tmp
dst[14] += tmp
hour := src[4]
tmp = hour / 10
dst[12] += hour - 10*tmp
dst[11] += tmp
fallthrough
case 4:
day := src[3]
tmp := day / 10
dst[9] += day - 10*tmp
dst[8] += tmp
month := src[2]
tmp = month / 10
dst[6] += month - 10*tmp
dst[5] += tmp
switch length {
case 10, 19, 21, 22, 23, 24, 25, 26:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s length %d", t, length)
}
switch len(src) {
case 4, 7, 11:
default:
t := "DATE"
if length > 10 {
t += "TIME"
}
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
}
dst = make([]byte, 0, length)
// start with the date
year := binary.LittleEndian.Uint16(src[:2])
tmp16 := year / 10
dst[3] += byte(year - 10*tmp16)
tmp16, year = tmp16/10, tmp16
dst[2] += byte(year - 10*tmp16)
tmp16, year = tmp16/10, tmp16
dst[1] += byte(year - 10*tmp16)
dst[0] += byte(tmp16)
pt = byte(year / 100)
p1 = byte(year - 100*uint16(pt))
p2, p3 = src[2], src[3]
dst = append(dst,
digits10[pt], digits01[pt],
digits10[p1], digits01[p1], '-',
digits10[p2], digits01[p2], '-',
digits10[p3], digits01[p3],
)
if length == 10 {
return dst, nil
}
if len(src) == 4 {
return append(dst, zeroDateTime[10:length]...), nil
}
dst = append(dst, ' ')
p1 = src[4] // hour
src = src[5:]
}
// p1 is 2-digit hour, src is after hour
p2, p3 = src[0], src[1]
dst = append(dst,
digits10[p1], digits01[p1], ':',
digits10[p2], digits01[p2], ':',
digits10[p3], digits01[p3],
)
if length <= byte(len(dst)) {
return dst, nil
}
var t string
if withTime {
t = "DATETIME"
} else {
t = "DATE"
src = src[2:]
if len(src) == 0 {
return append(dst, zeroDateTime[19:zOffs+length]...), nil
}
microsecs := binary.LittleEndian.Uint32(src[:4])
p1 = byte(microsecs / 10000)
microsecs -= 10000 * uint32(p1)
p2 = byte(microsecs / 100)
microsecs -= 100 * uint32(p2)
p3 = byte(microsecs)
switch decimals := zOffs + length - 20; decimals {
default:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3], digits01[p3],
), nil
case 1:
return append(dst, '.',
digits10[p1],
), nil
case 2:
return append(dst, '.',
digits10[p1], digits01[p1],
), nil
case 3:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2],
), nil
case 4:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
), nil
case 5:
return append(dst, '.',
digits10[p1], digits01[p1],
digits10[p2], digits01[p2],
digits10[p3],
), nil
}
return nil, fmt.Errorf("invalid %s-packet length %d", t, len(src))
}
/******************************************************************************
@@ -683,6 +544,10 @@ func skipLengthEncodedString(b []byte) (int, error) {
// returns the number read, whether the value is NULL and the number of bytes read
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
// See issue #349
if len(b) == 0 {
return 0, true, 1
}
switch b[0] {
// 251: NULL
@@ -724,3 +589,152 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
}
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
// If cap(buf) is not enough, reallocate new buffer.
func reserveBuffer(buf []byte, appendSize int) []byte {
newSize := len(buf) + appendSize
if cap(buf) < newSize {
// Grow buffer exponentially
newBuf := make([]byte, len(buf)*2+appendSize)
copy(newBuf, buf)
buf = newBuf
}
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 {
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++
}
}
return buf[:pos]
}
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
func escapeStringBackslash(buf []byte, v string) []byte {
pos := len(buf)
buf = reserveBuffer(buf, len(v)*2)
for i := 0; i < len(v); i++ {
c := v[i]
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++
}
}
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)
buf = reserveBuffer(buf, len(v)*2)
for i := 0; i < len(v); i++ {
c := v[i]
if c == '\'' {
buf[pos] = '\''
buf[pos+1] = '\''
pos += 2
} else {
buf[pos] = c
pos++
}
}
return buf[:pos]
}

View File

@@ -16,76 +16,6 @@ import (
"time"
)
var testDSNs = []struct {
in string
out string
loc *time.Location
}{
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"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] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"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] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:password@/dbname?loc=UTC&timeout=30s&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[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true}", time.UTC},
{"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[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.Local},
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
}
func TestDSNParser(t *testing.T) {
var cfg *config
var err error
var res string
for i, tst := range testDSNs {
cfg, err = parseDSN(tst.in)
if err != nil {
t.Error(err.Error())
}
// pointer not static
cfg.tls = nil
res = fmt.Sprintf("%+v", cfg)
if res != fmt.Sprintf(tst.out, tst.loc) {
t.Errorf("%d. parseDSN(%q) => %q, want %q", i, tst.in, res, fmt.Sprintf(tst.out, tst.loc))
}
}
}
func TestDSNParserInvalid(t *testing.T) {
var invalidDSNs = []string{
"@net(addr/", // no closing brace
"@tcp(/", // no closing brace
"tcp(/", // no closing brace
"(/", // no closing brace
"net(addr)//", // unescaped
"user:pass@tcp(1.2.3.4:3306)", // no trailing slash
//"/dbname?arg=/some/unescaped/path",
}
for i, tst := range invalidDSNs {
if _, err := parseDSN(tst); err == nil {
t.Errorf("invalid DSN #%d. (%s) didn't error!", i, tst)
}
}
}
func BenchmarkParseDSN(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, tst := range testDSNs {
if _, err := parseDSN(tst.in); err != nil {
b.Error(err.Error())
}
}
}
}
func TestScanNullTime(t *testing.T) {
var scanTests = []struct {
in interface{}
@@ -191,22 +121,77 @@ func TestFormatBinaryDateTime(t *testing.T) {
rawDate[5] = 46 // minutes
rawDate[6] = 23 // seconds
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
expect := func(expected string, length int, withTime bool) {
actual, _ := formatBinaryDateTime(rawDate[:length], withTime)
expect := func(expected string, inlen, outlen uint8) {
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
bytes, ok := actual.([]byte)
if !ok {
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
}
if string(bytes) != expected {
t.Errorf(
"expected %q, got %q for length %d, withTime %v",
bytes, actual, length, withTime,
"expected %q, got %q for length in %d, out %d",
bytes, actual, inlen, outlen,
)
}
}
expect("0000-00-00", 0, false)
expect("0000-00-00 00:00:00", 0, true)
expect("1978-12-30", 4, false)
expect("1978-12-30 15:46:23", 7, true)
expect("1978-12-30 15:46:23.987654", 11, true)
expect("0000-00-00", 0, 10)
expect("0000-00-00 00:00:00", 0, 19)
expect("1978-12-30", 4, 10)
expect("1978-12-30 15:46:23", 7, 19)
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)))
if actual != expected {
t.Errorf(
"expected %s, got %s",
expected, actual,
)
}
actual = string(escapeStringBackslash([]byte{}, value))
if actual != expected {
t.Errorf(
"expected %s, got %s",
expected, actual,
)
}
}
expect("foo\\0bar", "foo\x00bar")
expect("foo\\nbar", "foo\nbar")
expect("foo\\rbar", "foo\rbar")
expect("foo\\Zbar", "foo\x1abar")
expect("foo\\\"bar", "foo\"bar")
expect("foo\\\\bar", "foo\\bar")
expect("foo\\'bar", "foo'bar")
}
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))
if actual != expected {
t.Errorf(
"expected %s, got %s",
expected, actual,
)
}
}
expect("foo\x00bar", "foo\x00bar") // not affected
expect("foo\nbar", "foo\nbar") // not affected
expect("foo\rbar", "foo\rbar") // not affected
expect("foo\x1abar", "foo\x1abar") // not affected
expect("foo''bar", "foo'bar") // affected
expect("foo\"bar", "foo\"bar") // not affected
}

View File

@@ -984,6 +984,9 @@ func (s3 *S3) run(req *request, resp interface{}) (*http.Response, error) {
if v, ok := req.headers["Content-Length"]; ok {
hreq.ContentLength, _ = strconv.ParseInt(v[0], 10, 64)
delete(req.headers, "Content-Length")
if hreq.ContentLength == 0 {
req.payload = nil
}
}
if req.payload != nil {
hreq.Body = ioutil.NopCloser(req.payload)

View File

@@ -209,6 +209,7 @@ func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error
g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true)
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
g.Points = g.Points[:len(g.Points)-4]
// TODO: also trim g.InFontUnits and g.Unhinted?
return nil
}
@@ -282,6 +283,10 @@ func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) {
program = glyf[offset : offset+instrLen]
offset += instrLen
if ne == 0 {
return program
}
np0 := len(g.Points)
np1 := np0 + int(g.Ends[len(g.Ends)-1])

View File

@@ -6,9 +6,14 @@ import (
"runtime/debug"
)
// RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
type RecoveryHandlerLogger interface {
Println(...interface{})
}
type recoveryHandler struct {
handler http.Handler
logger *log.Logger
logger RecoveryHandlerLogger
printStack bool
}
@@ -46,7 +51,7 @@ func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
// RecoveryLogger is a functional option to override
// the default logger
func RecoveryLogger(logger *log.Logger) RecoveryOption {
func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption {
return func(h http.Handler) {
r := h.(*recoveryHandler)
r.logger = logger
@@ -73,11 +78,11 @@ func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.handler.ServeHTTP(w, req)
}
func (h recoveryHandler) log(message interface{}) {
func (h recoveryHandler) log(v ...interface{}) {
if h.logger != nil {
h.logger.Println(message)
h.logger.Println(v...)
} else {
log.Println(message)
log.Println(v...)
}
if h.printStack {

View File

@@ -23,6 +23,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Listing Routes](#listing-routes)
* [Static Files](#static-files)
* [Registered URLs](#registered-urls)
* [Full Example](#full-example)
@@ -65,8 +66,11 @@ r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
```go
vars := mux.Vars(request)
category := vars["category"]
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
```
And this is all you need to know about the basic usage. More advanced options are explained below.
@@ -164,6 +168,42 @@ s.HandleFunc("/{key}/", ProductHandler)
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### Listing Routes
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
```go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/products", handler)
r.HandleFunc("/articles", handler)
r.HandleFunc("/articles/{id}", handler)
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
t, err := route.GetPathTemplate()
if err != nil {
return err
}
fmt.Println(t)
return nil
})
http.Handle("/", r)
}
```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling

View File

@@ -57,6 +57,11 @@ calling mux.Vars():
vars := mux.Vars(request)
category := vars["category"]
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
when capturing groups were present.
And this is all you need to know about the basic usage. More advanced options
are explained below.

View File

@@ -1389,6 +1389,16 @@ func TestSubrouterErrorHandling(t *testing.T) {
}
}
// See: https://github.com/gorilla/mux/issues/200
func TestPanicOnCapturingGroups(t *testing.T) {
defer func() {
if recover() == nil {
t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed sucessfully.\n")
}
}()
NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json")
}
// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

View File

@@ -109,6 +109,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
if errCompile != nil {
return nil, errCompile
}
// Check for capturing groups which used to work in older versions
if reg.NumSubexp() != len(idxs)/2 {
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
}
// Done!
return &routeRegexp{
template: template,

View File

@@ -1,4 +1,4 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -9,6 +9,11 @@ import (
"errors"
"io"
"strings"
"sync"
)
var (
flateWriterPool = sync.Pool{}
)
func decompressNoContextTakeover(r io.Reader) io.Reader {
@@ -17,13 +22,20 @@ func decompressNoContextTakeover(r io.Reader) io.Reader {
"\x00\x00\xff\xff" +
// Add final block to squelch unexpected EOF error from flate reader.
"\x01\x00\x00\xff\xff"
return flate.NewReader(io.MultiReader(r, strings.NewReader(tail)))
}
func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) {
tw := &truncWriter{w: w}
fw, err := flate.NewWriter(tw, 3)
i := flateWriterPool.Get()
var fw *flate.Writer
var err error
if i == nil {
fw, err = flate.NewWriter(tw, 3)
} else {
fw = i.(*flate.Writer)
fw.Reset(tw)
}
return &flateWrapper{fw: fw, tw: tw}, err
}
@@ -69,11 +81,19 @@ type flateWrapper struct {
}
func (w *flateWrapper) Write(p []byte) (int, error) {
if w.fw == nil {
return 0, errWriteClosed
}
return w.fw.Write(p)
}
func (w *flateWrapper) Close() error {
if w.fw == nil {
return errWriteClosed
}
err1 := w.fw.Flush()
flateWriterPool.Put(w.fw)
w.fw = nil
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
}

View File

@@ -218,6 +218,7 @@ func isValidReceivedCloseCode(code int) bool {
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
}
// The Conn type represents a WebSocket connection.
type Conn struct {
conn net.Conn
isServer bool
@@ -406,12 +407,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
return err
}
// NextWriter returns a writer for the next message to send. The writer's Close
// method flushes the complete message to the network.
//
// There can be at most one open writer on a connection. NextWriter closes the
// previous writer if the application has not already done so.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
func (c *Conn) prepWrite(messageType int) error {
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
@@ -421,13 +417,22 @@ func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
}
if !isControl(messageType) && !isData(messageType) {
return nil, errBadWriteOpCode
return errBadWriteOpCode
}
c.writeErrMu.Lock()
err := c.writeErr
c.writeErrMu.Unlock()
if err != nil {
return err
}
// NextWriter returns a writer for the next message to send. The writer's Close
// method flushes the complete message to the network.
//
// There can be at most one open writer on a connection. NextWriter closes the
// previous writer if the application has not already done so.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
if err := c.prepWrite(messageType); err != nil {
return nil, err
}
@@ -652,16 +657,23 @@ func (w *messageWriter) Close() error {
// WriteMessage is a helper method for getting a writer using NextWriter,
// writing the message and closing the writer.
func (c *Conn) WriteMessage(messageType int, data []byte) error {
w, err := c.NextWriter(messageType)
if err != nil {
return err
}
if mw, ok := w.(*messageWriter); ok && c.isServer {
// Optimize write as a single frame.
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
// Fast path with no allocations and single frame.
if err := c.prepWrite(messageType); err != nil {
return err
}
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
n := copy(c.writeBuf[mw.pos:], data)
mw.pos += n
data = data[n:]
err = mw.flushFrame(true, data)
return mw.flushFrame(true, data)
}
w, err := c.NextWriter(messageType)
if err != nil {
return err
}
if _, err = w.Write(data); err != nil {

14
vendor/github.com/lib/pq/.travis.yml generated vendored
View File

@@ -1,10 +1,11 @@
language: go
go:
- 1.5
- 1.6
- 1.7
- tip
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- master
sudo: true
@@ -15,6 +16,7 @@ env:
- PQSSLCERTTEST_PATH=$PWD/certs
- PGHOST=127.0.0.1
matrix:
- PGVERSION=9.6
- PGVERSION=9.5
- PGVERSION=9.4
- PGVERSION=9.3
@@ -39,5 +41,5 @@ script:
- >
goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
- go vet ./...
- PQTEST_BINARY_PARAMETERS=no go test -v ./...
- PQTEST_BINARY_PARAMETERS=yes go test -v ./...
- PQTEST_BINARY_PARAMETERS=no go test -race -v ./...
- PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...

45
vendor/github.com/lib/pq/array.go generated vendored
View File

@@ -70,6 +70,9 @@ func (a *BoolArray) Scan(src interface{}) error {
return a.scanBytes(src)
case string:
return a.scanBytes([]byte(src))
case nil:
*a = nil
return nil
}
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
@@ -80,7 +83,7 @@ func (a *BoolArray) scanBytes(src []byte) error {
if err != nil {
return err
}
if len(elems) == 0 {
if *a != nil && len(elems) == 0 {
*a = (*a)[:0]
} else {
b := make(BoolArray, len(elems))
@@ -141,6 +144,9 @@ func (a *ByteaArray) Scan(src interface{}) error {
return a.scanBytes(src)
case string:
return a.scanBytes([]byte(src))
case nil:
*a = nil
return nil
}
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
@@ -151,7 +157,7 @@ func (a *ByteaArray) scanBytes(src []byte) error {
if err != nil {
return err
}
if len(elems) == 0 {
if *a != nil && len(elems) == 0 {
*a = (*a)[:0]
} else {
b := make(ByteaArray, len(elems))
@@ -210,6 +216,9 @@ func (a *Float64Array) Scan(src interface{}) error {
return a.scanBytes(src)
case string:
return a.scanBytes([]byte(src))
case nil:
*a = nil
return nil
}
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
@@ -220,7 +229,7 @@ func (a *Float64Array) scanBytes(src []byte) error {
if err != nil {
return err
}
if len(elems) == 0 {
if *a != nil && len(elems) == 0 {
*a = (*a)[:0]
} else {
b := make(Float64Array, len(elems))
@@ -320,6 +329,11 @@ func (a GenericArray) Scan(src interface{}) error {
return a.scanBytes(src, dv)
case string:
return a.scanBytes([]byte(src), dv)
case nil:
if dv.Kind() == reflect.Slice {
dv.Set(reflect.Zero(dv.Type()))
return nil
}
}
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
@@ -386,7 +400,13 @@ func (a GenericArray) Value() (driver.Value, error) {
rv := reflect.ValueOf(a.A)
if k := rv.Kind(); k != reflect.Array && k != reflect.Slice {
switch rv.Kind() {
case reflect.Slice:
if rv.IsNil() {
return nil, nil
}
case reflect.Array:
default:
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
}
@@ -412,6 +432,9 @@ func (a *Int64Array) Scan(src interface{}) error {
return a.scanBytes(src)
case string:
return a.scanBytes([]byte(src))
case nil:
*a = nil
return nil
}
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
@@ -422,7 +445,7 @@ func (a *Int64Array) scanBytes(src []byte) error {
if err != nil {
return err
}
if len(elems) == 0 {
if *a != nil && len(elems) == 0 {
*a = (*a)[:0]
} else {
b := make(Int64Array, len(elems))
@@ -470,6 +493,9 @@ func (a *StringArray) Scan(src interface{}) error {
return a.scanBytes(src)
case string:
return a.scanBytes([]byte(src))
case nil:
*a = nil
return nil
}
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
@@ -480,7 +506,7 @@ func (a *StringArray) scanBytes(src []byte) error {
if err != nil {
return err
}
if len(elems) == 0 {
if *a != nil && len(elems) == 0 {
*a = (*a)[:0]
} else {
b := make(StringArray, len(elems))
@@ -639,6 +665,9 @@ Element:
for i < len(src) {
switch src[i] {
case '{':
if depth == len(dims) {
break Element
}
depth++
dims[depth-1] = 0
i++
@@ -680,11 +709,11 @@ Element:
}
for i < len(src) {
if bytes.HasPrefix(src[i:], del) {
if bytes.HasPrefix(src[i:], del) && depth > 0 {
dims[depth-1]++
i += len(del)
goto Element
} else if src[i] == '}' {
} else if src[i] == '}' && depth > 0 {
dims[depth-1]++
depth--
i++

View File

@@ -70,6 +70,10 @@ func TestParseArrayError(t *testing.T) {
{`{,}`, "unexpected ',' at offset 1"},
{`{,x}`, "unexpected ',' at offset 1"},
{`{x,}`, "unexpected '}' at offset 3"},
{`{x,{`, "unexpected '{' at offset 3"},
{`{x},`, "unexpected ',' at offset 3"},
{`{x}}`, "unexpected '}' at offset 3"},
{`{{x}`, "expected '}' at offset 4"},
{`{""x}`, "unexpected 'x' at offset 3"},
{`{{a},{b,c}}`, "multidimensional arrays must have elements with matching dimensions"},
} {
@@ -171,6 +175,30 @@ func TestBoolArrayScanUnsupported(t *testing.T) {
}
}
func TestBoolArrayScanEmpty(t *testing.T) {
var arr BoolArray
err := arr.Scan(`{}`)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr == nil || len(arr) != 0 {
t.Errorf("Expected empty, got %#v", arr)
}
}
func TestBoolArrayScanNil(t *testing.T) {
arr := BoolArray{true, true, true}
err := arr.Scan(nil)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr != nil {
t.Errorf("Expected nil, got %+v", arr)
}
}
var BoolArrayStringTests = []struct {
str string
arr BoolArray
@@ -300,6 +328,30 @@ func TestByteaArrayScanUnsupported(t *testing.T) {
}
}
func TestByteaArrayScanEmpty(t *testing.T) {
var arr ByteaArray
err := arr.Scan(`{}`)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr == nil || len(arr) != 0 {
t.Errorf("Expected empty, got %#v", arr)
}
}
func TestByteaArrayScanNil(t *testing.T) {
arr := ByteaArray{{2}, {6}, {0, 0}}
err := arr.Scan(nil)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr != nil {
t.Errorf("Expected nil, got %+v", arr)
}
}
var ByteaArrayStringTests = []struct {
str string
arr ByteaArray
@@ -430,6 +482,30 @@ func TestFloat64ArrayScanUnsupported(t *testing.T) {
}
}
func TestFloat64ArrayScanEmpty(t *testing.T) {
var arr Float64Array
err := arr.Scan(`{}`)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr == nil || len(arr) != 0 {
t.Errorf("Expected empty, got %#v", arr)
}
}
func TestFloat64ArrayScanNil(t *testing.T) {
arr := Float64Array{5, 5, 5}
err := arr.Scan(nil)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr != nil {
t.Errorf("Expected nil, got %+v", arr)
}
}
var Float64ArrayStringTests = []struct {
str string
arr Float64Array
@@ -560,6 +636,30 @@ func TestInt64ArrayScanUnsupported(t *testing.T) {
}
}
func TestInt64ArrayScanEmpty(t *testing.T) {
var arr Int64Array
err := arr.Scan(`{}`)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr == nil || len(arr) != 0 {
t.Errorf("Expected empty, got %#v", arr)
}
}
func TestInt64ArrayScanNil(t *testing.T) {
arr := Int64Array{5, 5, 5}
err := arr.Scan(nil)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr != nil {
t.Errorf("Expected nil, got %+v", arr)
}
}
var Int64ArrayStringTests = []struct {
str string
arr Int64Array
@@ -689,6 +789,30 @@ func TestStringArrayScanUnsupported(t *testing.T) {
}
}
func TestStringArrayScanEmpty(t *testing.T) {
var arr StringArray
err := arr.Scan(`{}`)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr == nil || len(arr) != 0 {
t.Errorf("Expected empty, got %#v", arr)
}
}
func TestStringArrayScanNil(t *testing.T) {
arr := StringArray{"x", "x", "x"}
err := arr.Scan(nil)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if arr != nil {
t.Errorf("Expected nil, got %+v", arr)
}
}
var StringArrayStringTests = []struct {
str string
arr StringArray
@@ -811,6 +935,7 @@ func BenchmarkStringArrayValue(b *testing.B) {
func TestGenericArrayScanUnsupported(t *testing.T) {
var s string
var ss []string
var nsa [1]sql.NullString
for _, tt := range []struct {
src, dest interface{}
@@ -820,6 +945,7 @@ func TestGenericArrayScanUnsupported(t *testing.T) {
{nil, true, "destination bool is not a pointer to array or slice"},
{nil, &s, "destination *string is not a pointer to array or slice"},
{nil, ss, "destination []string is not a pointer to array or slice"},
{nil, &nsa, "<nil> to [1]sql.NullString"},
{true, &ss, "bool to []string"},
{`{{x}}`, &ss, "multidimensional ARRAY[1][1] is not implemented"},
{`{{x},{x}}`, &ss, "multidimensional ARRAY[2][1] is not implemented"},
@@ -862,6 +988,28 @@ func TestGenericArrayScanScannerArrayString(t *testing.T) {
}
}
func TestGenericArrayScanScannerSliceEmpty(t *testing.T) {
var nss []sql.NullString
if err := (GenericArray{&nss}).Scan(`{}`); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if nss == nil || len(nss) != 0 {
t.Errorf("Expected empty, got %#v", nss)
}
}
func TestGenericArrayScanScannerSliceNil(t *testing.T) {
nss := []sql.NullString{{String: ``, Valid: true}, {}}
if err := (GenericArray{&nss}).Scan(nil); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if nss != nil {
t.Errorf("Expected nil, got %+v", nss)
}
}
func TestGenericArrayScanScannerSliceBytes(t *testing.T) {
src, expected, nss := []byte(`{NULL,abc,"\""}`),
[]sql.NullString{{}, {String: `abc`, Valid: true}, {String: `"`, Valid: true}},
@@ -977,6 +1125,22 @@ func TestGenericArrayValue(t *testing.T) {
t.Errorf("Expected nil, got %q", result)
}
for _, tt := range []interface{}{
[]bool(nil),
[][]int(nil),
[]*int(nil),
[]sql.NullString(nil),
} {
result, err := GenericArray{tt}.Value()
if err != nil {
t.Fatalf("Expected no error for %#v, got %v", tt, err)
}
if result != nil {
t.Errorf("Expected nil for %#v, got %q", tt, result)
}
}
Tilde := func(v driver.Value) FuncArrayValuer {
return FuncArrayValuer{
func() string { return "~" },

189
vendor/github.com/lib/pq/conn.go generated vendored
View File

@@ -3,15 +3,12 @@ package pq
import (
"bufio"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"database/sql"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/user"
@@ -101,6 +98,15 @@ type conn struct {
namei int
scratch [512]byte
txnStatus transactionStatus
txnClosed chan<- struct{}
// Save connection arguments to use during CancelRequest.
dialer Dialer
opts values
// Cancellation key data for use with CancelRequest messages.
processID int
secretKey int
parameterStatus parameterStatus
@@ -310,7 +316,10 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
}
}
cn := &conn{}
cn := &conn{
opts: o,
dialer: d,
}
err = cn.handleDriverSettings(o)
if err != nil {
return nil, err
@@ -532,7 +541,15 @@ func (cn *conn) Begin() (_ driver.Tx, err error) {
return cn, nil
}
func (cn *conn) closeTxn() {
if cn.txnClosed != nil {
close(cn.txnClosed)
cn.txnClosed = nil
}
}
func (cn *conn) Commit() (err error) {
defer cn.closeTxn()
if cn.bad {
return driver.ErrBadConn
}
@@ -568,6 +585,7 @@ func (cn *conn) Commit() (err error) {
}
func (cn *conn) Rollback() (err error) {
defer cn.closeTxn()
if cn.bad {
return driver.ErrBadConn
}
@@ -718,6 +736,8 @@ func decideColumnFormats(colTyps []oid.Oid, forceText bool) (colFmts []format, c
case oid.T_int4:
fallthrough
case oid.T_int2:
fallthrough
case oid.T_uuid:
colFmts[i] = formatBinary
allText = false
@@ -797,7 +817,11 @@ func (cn *conn) Close() (err error) {
}
// Implement the "Queryer" interface
func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err error) {
func (cn *conn) Query(query string, args []driver.Value) (driver.Rows, error) {
return cn.query(query, args)
}
func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) {
if cn.bad {
return nil, driver.ErrBadConn
}
@@ -1000,42 +1024,12 @@ func (cn *conn) recv1() (t byte, r *readBuf) {
}
func (cn *conn) ssl(o values) {
verifyCaOnly := false
tlsConf := tls.Config{}
switch mode := o.Get("sslmode"); mode {
// "require" is the default.
case "", "require":
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
// Note: For backwards compatibility with earlier versions of PostgreSQL, if a
// root CA file exists, the behavior of sslmode=require will be the same as
// that of verify-ca, meaning the server certificate is validated against the
// CA. Relying on this behavior is discouraged, and applications that need
// certificate validation should always use verify-ca or verify-full.
if _, err := os.Stat(o.Get("sslrootcert")); err == nil {
verifyCaOnly = true
} else {
o.Set("sslrootcert", "")
}
case "verify-ca":
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
verifyCaOnly = true
case "verify-full":
tlsConf.ServerName = o.Get("host")
case "disable":
upgrade := ssl(o)
if upgrade == nil {
// Nothing to do
return
default:
errorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
}
cn.setupSSLClientCertificates(&tlsConf, o)
cn.setupSSLCA(&tlsConf, o)
w := cn.writeBuf(0)
w.int32(80877103)
cn.sendStartupPacket(w)
@@ -1050,114 +1044,7 @@ func (cn *conn) ssl(o values) {
panic(ErrSSLNotSupported)
}
client := tls.Client(cn.c, &tlsConf)
if verifyCaOnly {
cn.verifyCA(client, &tlsConf)
}
cn.c = client
}
// verifyCA carries out a TLS handshake to the server and verifies the
// presented certificate against the effective CA, i.e. the one specified in
// sslrootcert or the system CA if sslrootcert was not specified.
func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) {
err := client.Handshake()
if err != nil {
panic(err)
}
certs := client.ConnectionState().PeerCertificates
opts := x509.VerifyOptions{
DNSName: client.ConnectionState().ServerName,
Intermediates: x509.NewCertPool(),
Roots: tlsConf.RootCAs,
}
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
if err != nil {
panic(err)
}
}
// This function sets up SSL client certificates based on either the "sslkey"
// and "sslcert" settings (possibly set via the environment variables PGSSLKEY
// and PGSSLCERT, respectively), or if they aren't set, from the .postgresql
// directory in the user's home directory. If the file paths are set
// explicitly, the files must exist. The key file must also not be
// world-readable, or this function will panic with
// ErrSSLKeyHasWorldPermissions.
func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) {
var missingOk bool
sslkey := o.Get("sslkey")
sslcert := o.Get("sslcert")
if sslkey != "" && sslcert != "" {
// If the user has set an sslkey and sslcert, they *must* exist.
missingOk = false
} else {
// Automatically load certificates from ~/.postgresql.
user, err := user.Current()
if err != nil {
// user.Current() might fail when cross-compiling. We have to
// ignore the error and continue without client certificates, since
// we wouldn't know where to load them from.
return
}
sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
missingOk = true
}
// Check that both files exist, and report the error or stop, depending on
// which behaviour we want. Note that we don't do any more extensive
// checks than this (such as checking that the paths aren't directories);
// LoadX509KeyPair() will take care of the rest.
keyfinfo, err := os.Stat(sslkey)
if err != nil && missingOk {
return
} else if err != nil {
panic(err)
}
_, err = os.Stat(sslcert)
if err != nil && missingOk {
return
} else if err != nil {
panic(err)
}
// If we got this far, the key file must also have the correct permissions
kmode := keyfinfo.Mode()
if kmode != kmode&0600 {
panic(ErrSSLKeyHasWorldPermissions)
}
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
if err != nil {
panic(err)
}
tlsConf.Certificates = []tls.Certificate{cert}
}
// Sets up RootCAs in the TLS configuration if sslrootcert is set.
func (cn *conn) setupSSLCA(tlsConf *tls.Config, o values) {
if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" {
tlsConf.RootCAs = x509.NewCertPool()
cert, err := ioutil.ReadFile(sslrootcert)
if err != nil {
panic(err)
}
ok := tlsConf.RootCAs.AppendCertsFromPEM(cert)
if !ok {
errorf("couldn't parse pem in sslrootcert")
}
}
cn.c = upgrade(cn.c)
}
// isDriverSetting returns true iff a setting is purely for configuring the
@@ -1212,6 +1099,7 @@ func (cn *conn) startup(o values) {
t, r := cn.recv()
switch t {
case 'K':
cn.processBackendKeyData(r)
case 'S':
cn.processParameterStatus(r)
case 'R':
@@ -1439,6 +1327,7 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) {
type rows struct {
cn *conn
closed chan<- struct{}
colNames []string
colTyps []oid.Oid
colFmts []format
@@ -1447,6 +1336,9 @@ type rows struct {
}
func (rs *rows) Close() error {
if rs.closed != nil {
defer close(rs.closed)
}
// no need to look at cn.bad as Next() will
for {
err := rs.Next(nil)
@@ -1651,6 +1543,11 @@ func (cn *conn) readReadyForQuery() {
}
}
func (c *conn) processBackendKeyData(r *readBuf) {
c.processID = r.int32()
c.secretKey = r.int32()
}
func (cn *conn) readParseResponse() {
t, r := cn.recv1()
switch t {

92
vendor/github.com/lib/pq/conn_go18.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
// +build go1.8
package pq
import (
"context"
"database/sql/driver"
"errors"
)
// Implement the "QueryerContext" interface
func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
list := make([]driver.Value, len(args))
for i, nv := range args {
list[i] = nv.Value
}
var closed chan<- struct{}
if ctx.Done() != nil {
closed = watchCancel(ctx, cn.cancel)
}
r, err := cn.query(query, list)
if err != nil {
return nil, err
}
r.closed = closed
return r, nil
}
// Implement the "ExecerContext" interface
func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
list := make([]driver.Value, len(args))
for i, nv := range args {
list[i] = nv.Value
}
if ctx.Done() != nil {
closed := watchCancel(ctx, cn.cancel)
defer close(closed)
}
return cn.Exec(query, list)
}
// Implement the "ConnBeginTx" interface
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if opts.Isolation != 0 {
return nil, errors.New("isolation levels not supported")
}
if opts.ReadOnly {
return nil, errors.New("read-only transactions not supported")
}
tx, err := cn.Begin()
if err != nil {
return nil, err
}
if ctx.Done() != nil {
cn.txnClosed = watchCancel(ctx, cn.cancel)
}
return tx, nil
}
func watchCancel(ctx context.Context, cancel func()) chan<- struct{} {
closed := make(chan struct{})
go func() {
select {
case <-ctx.Done():
cancel()
case <-closed:
}
}()
return closed
}
func (cn *conn) cancel() {
var err error
can := &conn{}
can.c, err = dial(cn.dialer, cn.opts)
if err != nil {
return
}
can.ssl(cn.opts)
defer can.errRecover(&err)
w := can.writeBuf(0)
w.int32(80877102) // cancel request code
w.int32(cn.processID)
w.int32(cn.secretKey)
can.sendStartupPacket(w)
_ = can.c.Close()
}

27
vendor/github.com/lib/pq/copy.go generated vendored
View File

@@ -97,13 +97,13 @@ awaitCopyInResponse:
err = parseError(r)
case 'Z':
if err == nil {
cn.bad = true
ci.setBad()
errorf("unexpected ReadyForQuery in response to COPY")
}
cn.processReadyForQuery(r)
return nil, err
default:
cn.bad = true
ci.setBad()
errorf("unknown response for copy query: %q", t)
}
}
@@ -122,7 +122,7 @@ awaitCopyInResponse:
cn.processReadyForQuery(r)
return nil, err
default:
cn.bad = true
ci.setBad()
errorf("unknown response for CopyFail: %q", t)
}
}
@@ -143,7 +143,7 @@ func (ci *copyin) resploop() {
var r readBuf
t, err := ci.cn.recvMessage(&r)
if err != nil {
ci.cn.bad = true
ci.setBad()
ci.setError(err)
ci.done <- true
return
@@ -161,7 +161,7 @@ func (ci *copyin) resploop() {
err := parseError(&r)
ci.setError(err)
default:
ci.cn.bad = true
ci.setBad()
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
ci.done <- true
return
@@ -169,6 +169,19 @@ func (ci *copyin) resploop() {
}
}
func (ci *copyin) setBad() {
ci.Lock()
ci.cn.bad = true
ci.Unlock()
}
func (ci *copyin) isBad() bool {
ci.Lock()
b := ci.cn.bad
ci.Unlock()
return b
}
func (ci *copyin) isErrorSet() bool {
ci.Lock()
isSet := (ci.err != nil)
@@ -206,7 +219,7 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
return nil, errCopyInClosed
}
if ci.cn.bad {
if ci.isBad() {
return nil, driver.ErrBadConn
}
defer ci.cn.errRecover(&err)
@@ -244,7 +257,7 @@ func (ci *copyin) Close() (err error) {
}
ci.closed = true
if ci.cn.bad {
if ci.isBad() {
return driver.ErrBadConn
}
defer ci.cn.errRecover(&err)

8
vendor/github.com/lib/pq/encode.go generated vendored
View File

@@ -76,6 +76,12 @@ func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) inter
return int64(int32(binary.BigEndian.Uint32(s)))
case oid.T_int2:
return int64(int16(binary.BigEndian.Uint16(s)))
case oid.T_uuid:
b, err := decodeUUIDBinary(s)
if err != nil {
panic(err)
}
return b
default:
errorf("don't know how to decode binary parameter of type %d", uint32(typ))
@@ -471,7 +477,7 @@ func FormatTimestamp(t time.Time) []byte {
t = t.AddDate((-t.Year())*2+1, 0, 0)
bc = true
}
b := []byte(t.Format(time.RFC3339Nano))
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
_, offset := t.Zone()
offset = offset % 60

View File

@@ -141,22 +141,22 @@ var formatTimeTests = []struct {
time time.Time
expected string
}{
{time.Time{}, "0001-01-01T00:00:00Z"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03T04:05:06.123456789Z"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03T04:05:06.123456789+02:00"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03T04:05:06.123456789-06:00"},
{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03T04:05:06-07:30:09"},
{time.Time{}, "0001-01-01 00:00:00Z"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04:05:06.123456789-06:00"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z BC"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00 BC"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04:05:06.123456789-06:00 BC"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03T04:05:06-07:30:09"},
{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03T04:05:06-07:30:09 BC"},
{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
}
func TestFormatTs(t *testing.T) {
@@ -168,6 +168,26 @@ func TestFormatTs(t *testing.T) {
}
}
func TestFormatTsBackend(t *testing.T) {
db := openTestConn(t)
defer db.Close()
var str string
err := db.QueryRow("SELECT '2001-02-03T04:05:06.007-08:09:10'::time::text").Scan(&str)
if err == nil {
t.Fatalf("PostgreSQL is accepting an ISO timestamp input for time")
}
for i, tt := range formatTimeTests {
for _, typ := range []string{"date", "time", "timetz", "timestamp", "timestamptz"} {
err = db.QueryRow("SELECT $1::"+typ+"::text", tt.time).Scan(&str)
if err != nil {
t.Errorf("%d: incorrect time format for %v on the backend: %v", i, typ, err)
}
}
}
}
func TestTimestampWithTimeZone(t *testing.T) {
db := openTestConn(t)
defer db.Close()

View File

@@ -2,7 +2,12 @@
package pq
import "testing"
import (
"context"
"database/sql"
"testing"
"time"
)
func TestMultipleSimpleQuery(t *testing.T) {
db := openTestConn(t)
@@ -66,3 +71,95 @@ func TestMultipleSimpleQuery(t *testing.T) {
t.Fatal("unexpected result set")
}
}
func TestContextCancelExec(t *testing.T) {
db := openTestConn(t)
defer db.Close()
ctx, cancel := context.WithCancel(context.Background())
// Delay execution for just a bit until db.ExecContext has begun.
go func() {
time.Sleep(time.Millisecond * 10)
cancel()
}()
// Not canceled until after the exec has started.
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err.Error() != "pq: canceling statement due to user request" {
t.Fatalf("unexpected error: %s", err)
}
// Context is already canceled, so error should come before execution.
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err.Error() != "context canceled" {
t.Fatalf("unexpected error: %s", err)
}
}
func TestContextCancelQuery(t *testing.T) {
db := openTestConn(t)
defer db.Close()
ctx, cancel := context.WithCancel(context.Background())
// Delay execution for just a bit until db.QueryContext has begun.
go func() {
time.Sleep(time.Millisecond * 10)
cancel()
}()
// Not canceled until after the exec has started.
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err.Error() != "pq: canceling statement due to user request" {
t.Fatalf("unexpected error: %s", err)
}
// Context is already canceled, so error should come before execution.
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err.Error() != "context canceled" {
t.Fatalf("unexpected error: %s", err)
}
}
func TestContextCancelBegin(t *testing.T) {
db := openTestConn(t)
defer db.Close()
ctx, cancel := context.WithCancel(context.Background())
tx, err := db.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
// Delay execution for just a bit until tx.Exec has begun.
go func() {
time.Sleep(time.Millisecond * 10)
cancel()
}()
// Not canceled until after the exec has started.
if _, err := tx.Exec("select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err.Error() != "pq: canceling statement due to user request" {
t.Fatalf("unexpected error: %s", err)
}
// Transaction is canceled, so expect an error.
if _, err := tx.Query("select pg_sleep(1)"); err == nil {
t.Fatal("expected error")
} else if err != sql.ErrTxDone {
t.Fatalf("unexpected error: %s", err)
}
// Context is canceled, so cannot begin a transaction.
if _, err := db.BeginTx(ctx, nil); err == nil {
t.Fatal("expected error")
} else if err.Error() != "context canceled" {
t.Fatalf("unexpected error: %s", err)
}
}

175
vendor/github.com/lib/pq/ssl.go generated vendored Normal file
View File

@@ -0,0 +1,175 @@
package pq
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
)
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
// related settings. The function is nil when no upgrade should take place.
func ssl(o values) func(net.Conn) net.Conn {
verifyCaOnly := false
tlsConf := tls.Config{}
switch mode := o.Get("sslmode"); mode {
// "require" is the default.
case "", "require":
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
// Note: For backwards compatibility with earlier versions of PostgreSQL, if a
// root CA file exists, the behavior of sslmode=require will be the same as
// that of verify-ca, meaning the server certificate is validated against the
// CA. Relying on this behavior is discouraged, and applications that need
// certificate validation should always use verify-ca or verify-full.
if _, err := os.Stat(o.Get("sslrootcert")); err == nil {
verifyCaOnly = true
} else {
o.Set("sslrootcert", "")
}
case "verify-ca":
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
verifyCaOnly = true
case "verify-full":
tlsConf.ServerName = o.Get("host")
case "disable":
return nil
default:
errorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
}
sslClientCertificates(&tlsConf, o)
sslCertificateAuthority(&tlsConf, o)
sslRenegotiation(&tlsConf)
return func(conn net.Conn) net.Conn {
client := tls.Client(conn, &tlsConf)
if verifyCaOnly {
sslVerifyCertificateAuthority(client, &tlsConf)
}
return client
}
}
// sslClientCertificates adds the certificate specified in the "sslcert" and
// "sslkey" settings, or if they aren't set, from the .postgresql directory
// in the user's home directory. The configured files must exist and have
// the correct permissions.
func sslClientCertificates(tlsConf *tls.Config, o values) {
sslkey := o.Get("sslkey")
sslcert := o.Get("sslcert")
var cinfo, kinfo os.FileInfo
var err error
if sslcert != "" && sslkey != "" {
// Check that both files exist. Note that we don't do any more extensive
// checks than this (such as checking that the paths aren't directories);
// LoadX509KeyPair() will take care of the rest.
cinfo, err = os.Stat(sslcert)
if err != nil {
panic(err)
}
kinfo, err = os.Stat(sslkey)
if err != nil {
panic(err)
}
} else {
// Automatically find certificates from ~/.postgresql
sslcert, sslkey, cinfo, kinfo = sslHomeCertificates()
if cinfo == nil || kinfo == nil {
// No certificates to load
return
}
}
// The files must also have the correct permissions
sslCertificatePermissions(cinfo, kinfo)
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
if err != nil {
panic(err)
}
tlsConf.Certificates = []tls.Certificate{cert}
}
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
func sslCertificateAuthority(tlsConf *tls.Config, o values) {
if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" {
tlsConf.RootCAs = x509.NewCertPool()
cert, err := ioutil.ReadFile(sslrootcert)
if err != nil {
panic(err)
}
ok := tlsConf.RootCAs.AppendCertsFromPEM(cert)
if !ok {
errorf("couldn't parse pem in sslrootcert")
}
}
}
// sslHomeCertificates returns the path and stats of certificates in the current
// user's home directory.
func sslHomeCertificates() (cert, key string, cinfo, kinfo os.FileInfo) {
user, err := user.Current()
if err != nil {
// user.Current() might fail when cross-compiling. We have to ignore the
// error and continue without client certificates, since we wouldn't know
// from where to load them.
return
}
cert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
key = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
cinfo, err = os.Stat(cert)
if err != nil {
cinfo = nil
}
kinfo, err = os.Stat(key)
if err != nil {
kinfo = nil
}
return
}
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
// verifies the presented certificate against the CA, i.e. the one specified in
// sslrootcert or the system CA if sslrootcert was not specified.
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) {
err := client.Handshake()
if err != nil {
panic(err)
}
certs := client.ConnectionState().PeerCertificates
opts := x509.VerifyOptions{
DNSName: client.ConnectionState().ServerName,
Intermediates: x509.NewCertPool(),
Roots: tlsConf.RootCAs,
}
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
if err != nil {
panic(err)
}
}

14
vendor/github.com/lib/pq/ssl_go1.7.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// +build go1.7
package pq
import "crypto/tls"
// Accept renegotiation requests initiated by the backend.
//
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
// the default configuration of older versions has it enabled. Redshift
// also initiates renegotiations and cannot be reconfigured.
func sslRenegotiation(conf *tls.Config) {
conf.Renegotiation = tls.RenegotiateFreelyAsClient
}

16
vendor/github.com/lib/pq/ssl_permissions.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// +build !windows
package pq
import "os"
// sslCertificatePermissions checks the permissions on user-supplied certificate
// files. The key file should have very little access.
//
// libpq does not check key file permissions on Windows.
func sslCertificatePermissions(cert, key os.FileInfo) {
kmode := key.Mode()
if kmode != kmode&0600 {
panic(ErrSSLKeyHasWorldPermissions)
}
}

8
vendor/github.com/lib/pq/ssl_renegotiation.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// +build !go1.7
package pq
import "crypto/tls"
// Renegotiation is not supported by crypto/tls until Go 1.7.
func sslRenegotiation(*tls.Config) {}

9
vendor/github.com/lib/pq/ssl_windows.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// +build windows
package pq
import "os"
// sslCertificatePermissions checks the permissions on user-supplied certificate
// files. In libpq, this is a no-op on Windows.
func sslCertificatePermissions(cert, key os.FileInfo) {}

23
vendor/github.com/lib/pq/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package pq
import (
"encoding/hex"
"fmt"
)
// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format.
func decodeUUIDBinary(src []byte) ([]byte, error) {
if len(src) != 16 {
return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src))
}
dst := make([]byte, 36)
dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-'
hex.Encode(dst[0:], src[0:4])
hex.Encode(dst[9:], src[4:6])
hex.Encode(dst[14:], src[6:8])
hex.Encode(dst[19:], src[8:10])
hex.Encode(dst[24:], src[10:16])
return dst, nil
}

46
vendor/github.com/lib/pq/uuid_test.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
package pq
import (
"reflect"
"strings"
"testing"
)
func TestDecodeUUIDBinaryError(t *testing.T) {
t.Parallel()
_, err := decodeUUIDBinary([]byte{0x12, 0x34})
if err == nil {
t.Fatal("Expected error, got none")
}
if !strings.HasPrefix(err.Error(), "pq:") {
t.Errorf("Expected error to start with %q, got %q", "pq:", err.Error())
}
if !strings.Contains(err.Error(), "bad length: 2") {
t.Errorf("Expected error to contain length, got %q", err.Error())
}
}
func BenchmarkDecodeUUIDBinary(b *testing.B) {
x := []byte{0x03, 0xa3, 0x52, 0x2f, 0x89, 0x28, 0x49, 0x87, 0x84, 0xd6, 0x93, 0x7b, 0x36, 0xec, 0x27, 0x6f}
for i := 0; i < b.N; i++ {
decodeUUIDBinary(x)
}
}
func TestDecodeUUIDBackend(t *testing.T) {
db := openTestConn(t)
defer db.Close()
var s string = "a0ecc91d-a13f-4fe4-9fce-7e09777cc70a"
var scanned interface{}
err := db.QueryRow(`SELECT $1::uuid`, s).Scan(&scanned)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if !reflect.DeepEqual(scanned, []byte(s)) {
t.Errorf("Expected []byte(%q), got %T(%q)", s, scanned, scanned)
}
}

View File

@@ -1,7 +1,13 @@
language: go
sudo: false
go:
- 1.5
- 1.6
- 1.7
before_install:
# don't use the miekg/dns when testing forks
- mkdir -p $GOPATH/src/github.com/miekg
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
script:
- go test -race -v -bench=.

View File

@@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) [![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns)
[![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
# Alternative (more granular) approach to a DNS library
@@ -12,7 +13,7 @@ can build servers and resolvers with it.
We try to keep the "master" branch as sane as possible and at the bleeding edge
of standards, avoiding breaking changes wherever reasonable. We support the last
two versions of Go, currently: 1.5 and 1.6.
two versions of Go, currently: 1.6 and 1.7.
# Goals

View File

@@ -102,11 +102,11 @@ func (dns *Msg) SetAxfr(z string) *Msg {
// SetTsig appends a TSIG RR to the message.
// This is only a skeleton TSIG RR that is added as the last RR in the
// additional section. The Tsig is calculated when the message is being send.
func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg {
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
t := new(TSIG)
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
t.Algorithm = algo
t.Fudge = 300
t.Fudge = fudge
t.TimeSigned = uint64(timesigned)
t.OrigId = dns.Id
dns.Extra = append(dns.Extra, t)

View File

@@ -197,7 +197,7 @@ func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
// e := new(dns.EDNS0_SUBNET)
// e.Code = dns.EDNS0SUBNET
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
// e.NetMask = 32 // 32 for IPV4, 128 for IPv6
// e.SourceNetMask = 32 // 32 for IPV4, 128 for IPv6
// e.SourceScope = 0
// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4
// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6

29
vendor/github.com/miekg/dns/msg.go generated vendored
View File

@@ -203,12 +203,6 @@ func packDomainName(s string, msg []byte, off int, compression map[string]int, c
bs[j] = bs[j+2]
}
ls -= 2
} else if bs[i] == 't' {
bs[i] = '\t'
} else if bs[i] == 'r' {
bs[i] = '\r'
} else if bs[i] == 'n' {
bs[i] = '\n'
}
escapedDot = bs[i] == '.'
bsFresh = false
@@ -335,10 +329,6 @@ Loop:
fallthrough
case '"', '\\':
s = append(s, '\\', b)
case '\t':
s = append(s, '\\', 't')
case '\r':
s = append(s, '\\', 'r')
default:
if b < 32 || b >= 127 { // unprintable use \DDD
var buf [3]byte
@@ -431,12 +421,6 @@ func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) {
if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
msg[offset] = dddToByte(bs[i:])
i += 2
} else if bs[i] == 't' {
msg[offset] = '\t'
} else if bs[i] == 'r' {
msg[offset] = '\r'
} else if bs[i] == 'n' {
msg[offset] = '\n'
} else {
msg[offset] = bs[i]
}
@@ -508,12 +492,6 @@ func unpackTxtString(msg []byte, offset int) (string, int, error) {
switch b {
case '"', '\\':
s = append(s, '\\', b)
case '\t':
s = append(s, `\t`...)
case '\r':
s = append(s, `\r`...)
case '\n':
s = append(s, `\n`...)
default:
if b < 32 || b > 127 { // unprintable
var buf [3]byte
@@ -781,9 +759,6 @@ func (dns *Msg) Unpack(msg []byte) (err error) {
if dh, off, err = unpackMsgHdr(msg, off); err != nil {
return err
}
if off == len(msg) {
return ErrTruncated
}
dns.Id = dh.Id
dns.Response = (dh.Bits & _QR) != 0
@@ -797,6 +772,10 @@ func (dns *Msg) Unpack(msg []byte) (err error) {
dns.CheckingDisabled = (dh.Bits & _CD) != 0
dns.Rcode = int(dh.Bits & 0xF)
if off == len(msg) {
return ErrTruncated
}
// Optimistically use the count given to us in the header
dns.Question = make([]Question, 0, int(dh.Qdcount))

View File

@@ -117,9 +117,9 @@ return off, err
switch {
case st.Tag(i) == `dns:"-"`: // ignored
case st.Tag(i) == `dns:"cdomain-name"`:
fallthrough
case st.Tag(i) == `dns:"domain-name"`:
o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
case st.Tag(i) == `dns:"domain-name"`:
o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n")
case st.Tag(i) == `dns:"a"`:
o("off, err = packDataA(rr.%s, msg, off)\n")
case st.Tag(i) == `dns:"aaaa"`:

View File

@@ -263,8 +263,6 @@ func unpackString(msg []byte, off int) (string, int, error) {
switch b {
case '"', '\\':
s = append(s, '\\', b)
case '\t', '\r', '\n':
s = append(s, b)
default:
if b < 32 || b > 127 { // unprintable
var buf [3]byte

View File

@@ -86,7 +86,7 @@ func TestDomainName(t *testing.T) {
}
func TestDomainNameAndTXTEscapes(t *testing.T) {
tests := []byte{'.', '(', ')', ';', ' ', '@', '"', '\\', '\t', '\r', '\n', 0, 255}
tests := []byte{'.', '(', ')', ';', ' ', '@', '"', '\\', 9, 13, 10, 0, 255}
for _, b := range tests {
rrbytes := []byte{
1, b, 0, // owner
@@ -127,8 +127,8 @@ func TestTXTEscapeParsing(t *testing.T) {
test := [][]string{
{`";"`, `";"`},
{`\;`, `";"`},
{`"\t"`, `"\t"`},
{`"\r"`, `"\r"`},
{`"\t"`, `"t"`},
{`"\r"`, `"r"`},
{`"\ "`, `" "`},
{`"\;"`, `";"`},
{`"\;\""`, `";\""`},
@@ -137,8 +137,9 @@ func TestTXTEscapeParsing(t *testing.T) {
{`"(a\)"`, `"(a)"`},
{`"(a)"`, `"(a)"`},
{`"\048"`, `"0"`},
{`"\` + "\n" + `"`, `"\n"`},
{`"\` + "\r" + `"`, `"\r"`},
{`"\` + "\t" + `"`, `"\009"`},
{`"\` + "\n" + `"`, `"\010"`},
{`"\` + "\r" + `"`, `"\013"`},
{`"\` + "\x11" + `"`, `"\017"`},
{`"\'"`, `"'"`},
}
@@ -417,16 +418,16 @@ func TestQuotes(t *testing.T) {
tests := map[string]string{
`t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a bc\"",
`t.example.com. IN TXT "a
bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\n bc\"",
bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\010 bc\"",
`t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"",
`t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"",
`t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"",
`t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"",
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa \"",
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa aaa\"",
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa aaa\"",
`t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
`t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"",
`t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"",
"cid.urn.arpa. NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.",
"cid.urn.arpa. NAPTR 100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.",

View File

@@ -65,7 +65,7 @@ func TestNormalizedString(t *testing.T) {
tests := map[RR]string{
newRR(t, "mIEk.Nl. 3600 IN A 127.0.0.1"): "miek.nl.\tIN\tA\t127.0.0.1",
newRR(t, "m\\ iek.nL. 3600 IN A 127.0.0.1"): "m\\ iek.nl.\tIN\tA\t127.0.0.1",
newRR(t, "m\\\tIeK.nl. 3600 in A 127.0.0.1"): "m\\tiek.nl.\tIN\tA\t127.0.0.1",
newRR(t, "m\\\tIeK.nl. 3600 in A 127.0.0.1"): "m\\009iek.nl.\tIN\tA\t127.0.0.1",
}
for tc, expected := range tests {
n := normalizedString(tc)

View File

@@ -64,74 +64,63 @@ func endingToString(c chan lex, errstr, f string) (string, *ParseError, string)
return s, nil, l.comment
}
// A remainder of the rdata with embedded spaces, return the parsed string slice (sans the spaces)
// or an error
// A remainder of the rdata with embedded spaces, split on unquoted whitespace
// and return the parsed string slice or an error
func endingToTxtSlice(c chan lex, errstr, f string) ([]string, *ParseError, string) {
// Get the remaining data until we see a zNewline
quote := false
l := <-c
var s []string
if l.err {
return s, &ParseError{f, errstr, l}, ""
return nil, &ParseError{f, errstr, l}, ""
}
switch l.value == zQuote {
case true: // A number of quoted string
s = make([]string, 0)
empty := true
for l.value != zNewline && l.value != zEOF {
if l.err {
return nil, &ParseError{f, errstr, l}, ""
}
switch l.value {
case zString:
empty = false
if len(l.token) > 255 {
// split up tokens that are larger than 255 into 255-chunks
sx := []string{}
p, i := 0, 255
for {
if i <= len(l.token) {
sx = append(sx, l.token[p:i])
} else {
sx = append(sx, l.token[p:])
break
}
p, i = p+255, i+255
}
s = append(s, sx...)
break
}
s = append(s, l.token)
case zBlank:
if quote {
// zBlank can only be seen in between txt parts.
return nil, &ParseError{f, errstr, l}, ""
}
case zQuote:
if empty && quote {
s = append(s, "")
}
quote = !quote
empty = true
default:
return nil, &ParseError{f, errstr, l}, ""
}
l = <-c
}
if quote {
// Build the slice
s := make([]string, 0)
quote := false
empty := false
for l.value != zNewline && l.value != zEOF {
if l.err {
return nil, &ParseError{f, errstr, l}, ""
}
case false: // Unquoted text record
s = make([]string, 1)
for l.value != zNewline && l.value != zEOF {
if l.err {
return s, &ParseError{f, errstr, l}, ""
switch l.value {
case zString:
empty = false
if len(l.token) > 255 {
// split up tokens that are larger than 255 into 255-chunks
sx := []string{}
p, i := 0, 255
for {
if i <= len(l.token) {
sx = append(sx, l.token[p:i])
} else {
sx = append(sx, l.token[p:])
break
}
p, i = p+255, i+255
}
s = append(s, sx...)
break
}
s[0] += l.token
l = <-c
s = append(s, l.token)
case zBlank:
if quote {
// zBlank can only be seen in between txt parts.
return nil, &ParseError{f, errstr, l}, ""
}
case zQuote:
if empty && quote {
s = append(s, "")
}
quote = !quote
empty = true
default:
return nil, &ParseError{f, errstr, l}, ""
}
l = <-c
}
if quote {
return nil, &ParseError{f, errstr, l}, ""
}
return s, nil, l.comment
}
@@ -2027,9 +2016,12 @@ func setUINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
rr.Hdr = h
s, e, c1 := endingToTxtSlice(c, "bad UINFO Uinfo", f)
if e != nil {
return nil, e, ""
return nil, e, c1
}
rr.Uinfo = s[0] // silently discard anything above
if ln := len(s); ln == 0 {
return rr, nil, c1
}
rr.Uinfo = s[0] // silently discard anything after the first character-string
return rr, nil, c1
}

View File

@@ -339,7 +339,7 @@ func (srv *Server) ListenAndServe() error {
network := "tcp"
if srv.Net == "tcp4-tls" {
network = "tcp4"
} else if srv.Net == "tcp6" {
} else if srv.Net == "tcp6-tls" {
network = "tcp6"
}

View File

@@ -677,3 +677,43 @@ zDCJkckCgYEAndqM5KXGk5xYo+MAA1paZcbTUXwaWwjLU+XSRSSoyBEi5xMtfvUb
kFsxKCqxAnBVGEWAvVZAiiTOxleQFjz5RnL0BQp9Lg2cQe+dvuUmIAA=
-----END RSA PRIVATE KEY-----`)
)
func testShutdownBindPort(t *testing.T, protocol string, port string) {
handler := NewServeMux()
handler.HandleFunc(".", func(w ResponseWriter, r *Msg) {})
startedCh := make(chan struct{})
s := &Server{
Addr: net.JoinHostPort("127.0.0.1", port),
Net: protocol,
Handler: handler,
NotifyStartedFunc: func() {
startedCh <- struct{}{}
},
}
go func() {
if err := s.ListenAndServe(); err != nil {
t.Log(err)
}
}()
<-startedCh
t.Logf("DNS server is started on: %s", s.Addr)
if err := s.Shutdown(); err != nil {
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
go func() {
if err := s.ListenAndServe(); err != nil {
t.Fatal(err)
}
}()
<-startedCh
t.Logf("DNS server is started on: %s", s.Addr)
}
func TestShutdownBindPortUDP(t *testing.T) {
testShutdownBindPort(t, "udp", "1153")
}
func TestShutdownBindPortTCP(t *testing.T) {
testShutdownBindPort(t, "tcp", "1154")
}

19
vendor/github.com/miekg/dns/types.go generated vendored
View File

@@ -480,12 +480,6 @@ func appendDomainNameByte(s []byte, b byte) []byte {
func appendTXTStringByte(s []byte, b byte) []byte {
switch b {
case '\t':
return append(s, '\\', 't')
case '\r':
return append(s, '\\', 'r')
case '\n':
return append(s, '\\', 'n')
case '"', '\\':
return append(s, '\\', b)
}
@@ -525,17 +519,8 @@ func nextByte(b []byte, offset int) (byte, int) {
return dddToByte(b[offset+1:]), 4
}
}
// not \ddd, maybe a control char
switch b[offset+1] {
case 't':
return '\t', 2
case 'r':
return '\r', 2
case 'n':
return '\n', 2
default:
return b[offset+1], 2
}
// not \ddd, just an RFC 1035 "quoted" character
return b[offset+1], 2
}
type SPF struct {

View File

@@ -22,14 +22,17 @@ func setUDPSocketOptions4(conn *net.UDPConn) error {
return err
}
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil {
file.Close()
return err
}
// Calling File() above results in the connection becoming blocking, we must fix that.
// See https://github.com/miekg/dns/issues/279
err = syscall.SetNonblock(int(file.Fd()), true)
if err != nil {
file.Close()
return err
}
file.Close()
return nil
}
@@ -40,12 +43,15 @@ func setUDPSocketOptions6(conn *net.UDPConn) error {
return err
}
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
file.Close()
return err
}
err = syscall.SetNonblock(int(file.Fd()), true)
if err != nil {
file.Close()
return err
}
file.Close()
return nil
}
@@ -59,8 +65,10 @@ func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) {
// dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections
v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY)
if err != nil {
file.Close()
return false, err
}
file.Close()
return v6only == 1, nil
}
@@ -69,5 +77,6 @@ func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) {
if err != nil {
return nil, err
}
defer file.Close()
return syscall.Getsockname(int(file.Fd()))
}

34
vendor/github.com/miekg/dns/zmsg.go generated vendored
View File

@@ -221,7 +221,7 @@ func (rr *DNAME) pack(msg []byte, off int, compression map[string]int, compress
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.Target, msg, off, compression, compress)
off, err = PackDomainName(rr.Target, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -447,7 +447,7 @@ func (rr *KX) pack(msg []byte, off int, compression map[string]int, compress boo
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Exchanger, msg, off, compression, compress)
off, err = PackDomainName(rr.Exchanger, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -539,7 +539,7 @@ func (rr *LP) pack(msg []byte, off int, compression map[string]int, compress boo
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Fqdn, msg, off, compression, compress)
off, err = PackDomainName(rr.Fqdn, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -679,7 +679,7 @@ func (rr *NAPTR) pack(msg []byte, off int, compression map[string]int, compress
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Replacement, msg, off, compression, compress)
off, err = PackDomainName(rr.Replacement, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -753,7 +753,7 @@ func (rr *NSAPPTR) pack(msg []byte, off int, compression map[string]int, compres
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.Ptr, msg, off, compression, compress)
off, err = PackDomainName(rr.Ptr, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -767,7 +767,7 @@ func (rr *NSEC) pack(msg []byte, off int, compression map[string]int, compress b
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.NextDomain, msg, off, compression, compress)
off, err = PackDomainName(rr.NextDomain, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -905,11 +905,11 @@ func (rr *PX) pack(msg []byte, off int, compression map[string]int, compress boo
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Map822, msg, off, compression, compress)
off, err = PackDomainName(rr.Map822, msg, off, compression, false)
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Mapx400, msg, off, compression, compress)
off, err = PackDomainName(rr.Mapx400, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -963,11 +963,11 @@ func (rr *RP) pack(msg []byte, off int, compression map[string]int, compress boo
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.Mbox, msg, off, compression, compress)
off, err = PackDomainName(rr.Mbox, msg, off, compression, false)
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Txt, msg, off, compression, compress)
off, err = PackDomainName(rr.Txt, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1009,7 +1009,7 @@ func (rr *RRSIG) pack(msg []byte, off int, compression map[string]int, compress
if err != nil {
return off, err
}
off, err = PackDomainName(rr.SignerName, msg, off, compression, compress)
off, err = PackDomainName(rr.SignerName, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1073,7 +1073,7 @@ func (rr *SIG) pack(msg []byte, off int, compression map[string]int, compress bo
if err != nil {
return off, err
}
off, err = PackDomainName(rr.SignerName, msg, off, compression, compress)
off, err = PackDomainName(rr.SignerName, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1181,7 +1181,7 @@ func (rr *SRV) pack(msg []byte, off int, compression map[string]int, compress bo
if err != nil {
return off, err
}
off, err = PackDomainName(rr.Target, msg, off, compression, compress)
off, err = PackDomainName(rr.Target, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1243,11 +1243,11 @@ func (rr *TALINK) pack(msg []byte, off int, compression map[string]int, compress
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.PreviousName, msg, off, compression, compress)
off, err = PackDomainName(rr.PreviousName, msg, off, compression, false)
if err != nil {
return off, err
}
off, err = PackDomainName(rr.NextName, msg, off, compression, compress)
off, err = PackDomainName(rr.NextName, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1261,7 +1261,7 @@ func (rr *TKEY) pack(msg []byte, off int, compression map[string]int, compress b
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.Algorithm, msg, off, compression, compress)
off, err = PackDomainName(rr.Algorithm, msg, off, compression, false)
if err != nil {
return off, err
}
@@ -1333,7 +1333,7 @@ func (rr *TSIG) pack(msg []byte, off int, compression map[string]int, compress b
return off, err
}
headerEnd := off
off, err = PackDomainName(rr.Algorithm, msg, off, compression, compress)
off, err = PackDomainName(rr.Algorithm, msg, off, compression, false)
if err != nil {
return off, err
}

View File

@@ -3,7 +3,6 @@ language: go
os:
- linux
- osx
env:
- ARCH=x86_64
@@ -12,12 +11,9 @@ env:
go:
- 1.5.3
- 1.6
- 1.7.4
script:
- diff -au <(gofmt -d .) <(printf "")
- go vet ./...
- go test -short -race -v ./...
notifications:
slack:
secure: HrOX2k6F/sEl6Rr4m5vHOdJCIwV42be0kz1Jy/WSMvrl/fQ8YkldKviLeWh4aWt1kclsYhNQ4FqGML+RIZYsdOqej4fAw9Vi5pZkI1MzPJq0UjrtMqkqzvD90eDGQYCKwaXjEIN8cohwJeb6X0B0HKAd9sqJW5GH5SwnhH5WWP8=

View File

@@ -1,5 +1,5 @@
# Minio Golang Library for Amazon S3 Compatible Cloud Storage [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
The Minio Golang Client SDK provides simple APIs to access any Amazon S3 compatible object storage server.
# Minio Go Client SDK for Amazon S3 Compatible Cloud Storage [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
The Minio Go Client SDK provides simple APIs to access any Amazon S3 compatible object storage.
**Supported cloud storage providers:**
@@ -14,22 +14,21 @@ The Minio Golang Client SDK provides simple APIs to access any Amazon S3 compati
- Ceph Object Gateway
- Riak CS
This quickstart guide will show you how to install the Minio client SDK, connect to Minio, and provide a walkthrough of a simple file uploader. For a complete list of APIs and examples, please take a look at the [Golang Client API Reference](https://docs.minio.io/docs/golang-client-api-reference).
This quickstart guide will show you how to install the Minio client SDK, connect to Minio, and provide a walkthrough for a simple file uploader. For a complete list of APIs and examples, please take a look at the [Go Client API Reference](https://docs.minio.io/docs/golang-client-api-reference).
This document assumes that you have a working [Golang setup](https://docs.minio.io/docs/how-to-install-golang).
This document assumes that you have a working [Go development environment](https://docs.minio.io/docs/how-to-install-golang).
## Download from Github
```sh
$ go get -u github.com/minio/minio-go
go get -u github.com/minio/minio-go
```
## Initialize Minio Client
You need four items to connect to Minio object storage server.
Minio client requires the following four parameters specified to connect to an Amazon S3 compatible object storage.
| Parameter | Description|
@@ -68,7 +67,7 @@ func main() {
## Quick Start Example - File Uploader
This example program connects to an object storage server, makes a bucket on the server and then uploads a file to the bucket.
This example program connects to an object storage server, creates a bucket and uploads a file to the bucket.
@@ -132,11 +131,11 @@ func main() {
```sh
$ go run file-uploader.go
go run file-uploader.go
2016/08/13 17:03:28 Successfully created mymusic
2016/08/13 17:03:40 Successfully uploaded golden-oldies.zip of size 16253413
$ mc ls play/mymusic/
mc ls play/mymusic/
[2016-05-27 16:02:16 PDT] 17MiB golden-oldies.zip
```
@@ -191,6 +190,13 @@ The full API Reference is available here.
* [`PresignedPutObject`](https://docs.minio.io/docs/golang-client-api-reference#PresignedPutObject)
* [`PresignedPostPolicy`](https://docs.minio.io/docs/golang-client-api-reference#PresignedPostPolicy)
### API Reference : Client custom settings
* [`SetAppInfo`](http://docs.minio.io/docs/golang-client-api-reference#SetAppInfo)
* [`SetCustomTransport`](http://docs.minio.io/docs/golang-client-api-reference#SetCustomTransport)
* [`TraceOn`](http://docs.minio.io/docs/golang-client-api-reference#TraceOn)
* [`TraceOff`](http://docs.minio.io/docs/golang-client-api-reference#TraceOff)
## Full Examples
#### Full Examples : Bucket Operations
@@ -238,7 +244,7 @@ The full API Reference is available here.
## Explore Further
* [Complete Documentation](https://docs.minio.io)
* [Minio Golang Client SDK API Reference](https://docs.minio.io/docs/golang-client-api-reference)
* [Minio Go Client SDK API Reference](https://docs.minio.io/docs/golang-client-api-reference)
* [Go Music Player App- Full Application Example ](https://docs.minio.io/docs/go-music-player-app)
## Contribute

View File

@@ -16,7 +16,10 @@
package minio
import "time"
import (
"net/http"
"time"
)
// BucketInfo container for bucket metadata.
type BucketInfo struct {
@@ -38,6 +41,10 @@ type ObjectInfo struct {
Size int64 `json:"size"` // Size in bytes of the object.
ContentType string `json:"contentType"` // A standard MIME type describing the format of the object data.
// Collection of additional metadata on the object.
// eg: x-amz-meta-*, content-encoding etc.
Metadata http.Header `json:"metadata"`
// Owner name.
Owner struct {
DisplayName string `json:"name"`

View File

@@ -275,7 +275,7 @@ func (o *Object) setOffset(bytesRead int64) error {
return nil
}
// Read reads up to len(p) bytes into p. It returns the number of
// Read reads up to len(b) bytes into b. It returns the number of
// bytes read (0 <= n <= len(p)) and any error encountered. Returns
// io.EOF upon end of file.
func (o *Object) Read(b []byte) (n int, err error) {
@@ -343,7 +343,7 @@ func (o *Object) Read(b []byte) (n int, err error) {
return response.Size, err
}
// Stat returns the ObjectInfo structure describing object.
// Stat returns the ObjectInfo structure describing Object.
func (o *Object) Stat() (ObjectInfo, error) {
if o == nil {
return ObjectInfo{}, ErrInvalidArgument("Object is nil")

View File

@@ -41,7 +41,7 @@ func (c Client) GetBucketPolicy(bucketName, objectPrefix string) (bucketPolicy p
return policy.GetPolicy(policyInfo.Statements, bucketName, objectPrefix), nil
}
// GetBucketPolicy - get bucket policy rules at a given path.
// ListBucketPolicies - list all policies for a given prefix and all its children.
func (c Client) ListBucketPolicies(bucketName, objectPrefix string) (bucketPolicies map[string]policy.BucketPolicy, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
@@ -57,7 +57,7 @@ func (c Client) ListBucketPolicies(bucketName, objectPrefix string) (bucketPolic
return policy.GetPolicies(policyInfo.Statements, bucketName), nil
}
// Request server for policy.
// Request server for current bucket policy.
func (c Client) getBucketPolicy(bucketName string, objectPrefix string) (policy.BucketAccessPolicy, error) {
// Get resources properly escaped and lined up before
// using them in http request.

View File

@@ -482,6 +482,7 @@ func (c Client) listIncompleteUploads(bucketName, objectPrefix string, recursive
objectMultipartStatCh <- ObjectMultipartInfo{
Err: err,
}
continue
}
}
select {

View File

@@ -22,6 +22,9 @@ import (
"io"
"net/http"
"net/url"
"time"
"github.com/minio/minio-go/pkg/s3utils"
)
// GetBucketNotification - get bucket notification at a given path.
@@ -135,7 +138,7 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even
}
// Check ARN partition to verify if listening bucket is supported
if isAmazonEndpoint(c.endpointURL) || isGoogleEndpoint(c.endpointURL) {
if s3utils.IsAmazonEndpoint(c.endpointURL) || s3utils.IsGoogleEndpoint(c.endpointURL) {
notificationInfoCh <- NotificationInfo{
Err: ErrAPINotSupported("Listening bucket notification is specific only to `minio` partitions"),
}
@@ -143,7 +146,14 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even
}
// Continously run and listen on bucket notification.
for {
// Create a done channel to control 'ListObjects' go routine.
retryDoneCh := make(chan struct{}, 1)
// Indicate to our routine to exit cleanly upon return.
defer close(retryDoneCh)
// Wait on the jitter retry loop.
for range c.newRetryTimerContinous(time.Second, time.Second*30, MaxJitter, retryDoneCh) {
urlValues := make(url.Values)
urlValues.Set("prefix", prefix)
urlValues.Set("suffix", suffix)
@@ -155,10 +165,7 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even
queryValues: urlValues,
})
if err != nil {
notificationInfoCh <- NotificationInfo{
Err: err,
}
return
continue
}
// Validate http response, upon error return quickly.
@@ -180,10 +187,7 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even
for bio.Scan() {
var notificationInfo NotificationInfo
if err = json.Unmarshal(bio.Bytes(), &notificationInfo); err != nil {
notificationInfoCh <- NotificationInfo{
Err: err,
}
return
continue
}
// Send notifications on channel only if there are events received.
if len(notificationInfo.Records) > 0 {
@@ -200,12 +204,7 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even
// and re-connect.
if err == io.ErrUnexpectedEOF {
resp.Body.Close()
continue
}
notificationInfoCh <- NotificationInfo{
Err: err,
}
return
}
}
}(notificationInfoCh)

View File

@@ -20,6 +20,9 @@ import (
"errors"
"net/url"
"time"
"github.com/minio/minio-go/pkg/s3signer"
"github.com/minio/minio-go/pkg/s3utils"
)
// supportedGetReqParams - supported request parameters for GET presigned request.
@@ -126,14 +129,14 @@ func (c Client) PresignedPostPolicy(p *PostPolicy) (u *url.URL, formData map[str
policyBase64 := p.base64()
p.formData["policy"] = policyBase64
// For Google endpoint set this value to be 'GoogleAccessId'.
if isGoogleEndpoint(c.endpointURL) {
if s3utils.IsGoogleEndpoint(c.endpointURL) {
p.formData["GoogleAccessId"] = c.accessKeyID
} else {
// For all other endpoints set this value to be 'AWSAccessKeyId'.
p.formData["AWSAccessKeyId"] = c.accessKeyID
}
// Sign the policy.
p.formData["signature"] = postPresignSignatureV2(policyBase64, c.secretAccessKey)
p.formData["signature"] = s3signer.PostPresignSignatureV2(policyBase64, c.secretAccessKey)
return u, p.formData, nil
}
@@ -156,7 +159,7 @@ func (c Client) PresignedPostPolicy(p *PostPolicy) (u *url.URL, formData map[str
}
// Add a credential policy.
credential := getCredential(c.accessKeyID, location, t)
credential := s3signer.GetCredential(c.accessKeyID, location, t)
if err = p.addNewPolicy(policyCondition{
matchType: "eq",
condition: "$x-amz-credential",
@@ -172,6 +175,6 @@ func (c Client) PresignedPostPolicy(p *PostPolicy) (u *url.URL, formData map[str
p.formData["x-amz-algorithm"] = signV4Algorithm
p.formData["x-amz-credential"] = credential
p.formData["x-amz-date"] = t.Format(iso8601DateFormat)
p.formData["x-amz-signature"] = postPresignSignatureV4(policyBase64, t, c.secretAccessKey, location)
p.formData["x-amz-signature"] = s3signer.PostPresignSignatureV4(policyBase64, t, c.secretAccessKey, location)
return u, p.formData, nil
}

View File

@@ -26,8 +26,10 @@ import (
"io/ioutil"
"net/http"
"net/url"
"path"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/s3signer"
)
/// Bucket operations
@@ -89,11 +91,8 @@ func (c Client) makeBucketRequest(bucketName string, location string) (*http.Req
// is the preferred method here. The final location of the
// 'bucket' is provided through XML LocationConstraint data with
// the request.
targetURL, err := url.Parse(c.endpointURL)
if err != nil {
return nil, err
}
targetURL.Path = "/" + bucketName + "/"
targetURL := c.endpointURL
targetURL.Path = path.Join(bucketName, "") + "/"
// get a new HTTP request for the method.
req, err := http.NewRequest("PUT", targetURL.String(), nil)
@@ -133,9 +132,9 @@ func (c Client) makeBucketRequest(bucketName string, location string) (*http.Req
if c.signature.isV4() {
// Signature calculated for MakeBucket request should be for 'us-east-1',
// regardless of the bucket's location constraint.
req = signV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
} else if c.signature.isV2() {
req = signV2(*req, c.accessKeyID, c.secretAccessKey)
req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
}
// Return signed request.

View File

@@ -24,8 +24,10 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"testing"
"github.com/minio/minio-go/pkg/s3signer"
)
// Tests validate http request formulated for creation of bucket.
@@ -33,14 +35,11 @@ func TestMakeBucketRequest(t *testing.T) {
// Generates expected http request for bucket creation.
// Used for asserting with the actual request generated.
createExpectedRequest := func(c *Client, bucketName string, location string, req *http.Request) (*http.Request, error) {
targetURL, err := url.Parse(c.endpointURL)
if err != nil {
return nil, err
}
targetURL.Path = "/" + bucketName + "/"
targetURL := c.endpointURL
targetURL.Path = path.Join(bucketName, "") + "/"
// get a new HTTP request for the method.
var err error
req, err = http.NewRequest("PUT", targetURL.String(), nil)
if err != nil {
return nil, err
@@ -78,9 +77,9 @@ func TestMakeBucketRequest(t *testing.T) {
if c.signature.isV4() {
// Signature calculated for MakeBucket request should be for 'us-east-1',
// regardless of the bucket's location constraint.
req = signV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
} else if c.signature.isV2() {
req = signV2(*req, c.accessKeyID, c.secretAccessKey)
req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
}
// Return signed request.

View File

@@ -44,18 +44,17 @@ func isReadAt(reader io.Reader) (ok bool) {
}
// shouldUploadPart - verify if part should be uploaded.
func shouldUploadPart(objPart objectPart, objectParts map[int]objectPart) bool {
func shouldUploadPart(objPart objectPart, uploadReq uploadPartReq) bool {
// If part not found should upload the part.
uploadedPart, found := objectParts[objPart.PartNumber]
if !found {
if uploadReq.Part == nil {
return true
}
// if size mismatches should upload the part.
if objPart.Size != uploadedPart.Size {
if objPart.Size != uploadReq.Part.Size {
return true
}
// if md5sum mismatches should upload the part.
if objPart.ETag != uploadedPart.ETag {
if objPart.ETag != uploadReq.Part.ETag {
return true
}
return false
@@ -68,7 +67,7 @@ func shouldUploadPart(objPart objectPart, objectParts map[int]objectPart) bool {
// object storage it will have the following parameters as constants.
//
// maxPartsCount - 10000
// minPartSize - 5MiB
// minPartSize - 64MiB
// maxMultipartPutObjectSize - 5TiB
//
func optimalPartInfo(objectSize int64) (totalPartsCount int, partSize int64, lastPartSize int64, err error) {
@@ -167,37 +166,64 @@ func hashCopyN(hashAlgorithms map[string]hash.Hash, hashSums map[string][]byte,
// getUploadID - fetch upload id if already present for an object name
// or initiate a new request to fetch a new upload id.
func (c Client) getUploadID(bucketName, objectName, contentType string) (uploadID string, isNew bool, err error) {
func (c Client) newUploadID(bucketName, objectName string, metaData map[string][]string) (uploadID string, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return "", false, err
return "", err
}
if err := isValidObjectName(objectName); err != nil {
return "", false, err
return "", err
}
// Set content Type to default if empty string.
if contentType == "" {
contentType = "application/octet-stream"
}
// Find upload id for previous upload for an object.
uploadID, err = c.findUploadID(bucketName, objectName)
// Initiate multipart upload for an object.
initMultipartUploadResult, err := c.initiateMultipartUpload(bucketName, objectName, metaData)
if err != nil {
return "", false, err
return "", err
}
return initMultipartUploadResult.UploadID, nil
}
// getMpartUploadSession returns the upload id and the uploaded parts to continue a previous upload session
// or initiate a new multipart session if no current one found
func (c Client) getMpartUploadSession(bucketName, objectName string, metaData map[string][]string) (string, map[int]objectPart, error) {
// A map of all uploaded parts.
var partsInfo map[int]objectPart
var err error
uploadID, err := c.findUploadID(bucketName, objectName)
if err != nil {
return "", nil, err
}
if uploadID == "" {
// Initiate multipart upload for an object.
initMultipartUploadResult, err := c.initiateMultipartUpload(bucketName, objectName, contentType)
// Initiates a new multipart request
uploadID, err = c.newUploadID(bucketName, objectName, metaData)
if err != nil {
return "", false, err
return "", nil, err
}
} else {
// Fetch previously upload parts and maximum part size.
partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
if err != nil {
// When the server returns NoSuchUpload even if its previouls acknowleged the existance of the upload id,
// initiate a new multipart upload
if respErr, ok := err.(ErrorResponse); ok && respErr.Code == "NoSuchUpload" {
uploadID, err = c.newUploadID(bucketName, objectName, metaData)
if err != nil {
return "", nil, err
}
} else {
return "", nil, err
}
}
// Save the new upload id.
uploadID = initMultipartUploadResult.UploadID
// Indicate that this is a new upload id.
isNew = true
}
return uploadID, isNew, nil
// Allocate partsInfo if not done yet
if partsInfo == nil {
partsInfo = make(map[int]objectPart)
}
return uploadID, partsInfo, nil
}
// computeHash - Calculates hashes for an input read Seeker.

View File

@@ -16,7 +16,11 @@
package minio
import "net/http"
import (
"net/http"
"github.com/minio/minio-go/pkg/s3utils"
)
// CopyObject - copy a source object into a new object with the provided name in the provided bucket
func (c Client) CopyObject(bucketName string, objectName string, objectSource string, cpCond CopyConditions) error {
@@ -38,7 +42,7 @@ func (c Client) CopyObject(bucketName string, objectName string, objectSource st
}
// Set copy source.
customHeaders.Set("x-amz-copy-source", urlEncodePath(objectSource))
customHeaders.Set("x-amz-copy-source", s3utils.EncodePath(objectSource))
// Execute PUT on objectName.
resp, err := c.executeMethod("PUT", requestMetadata{

View File

@@ -28,6 +28,8 @@ import (
"os"
"path/filepath"
"sort"
"github.com/minio/minio-go/pkg/s3utils"
)
// FPutObject - Create an object in a bucket, with contents from file at filePath.
@@ -62,6 +64,8 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
return 0, ErrEntityTooLarge(fileSize, maxMultipartPutObjectSize, bucketName, objectName)
}
objMetadata := make(map[string][]string)
// Set contentType based on filepath extension if not given or default
// value of "binary/octet-stream" if the extension has no associated type.
if contentType == "" {
@@ -70,9 +74,11 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
}
}
objMetadata["Content-Type"] = []string{contentType}
// NOTE: Google Cloud Storage multipart Put is not compatible with Amazon S3 APIs.
// Current implementation will only upload a maximum of 5GiB to Google Cloud Storage servers.
if isGoogleEndpoint(c.endpointURL) {
if s3utils.IsGoogleEndpoint(c.endpointURL) {
if fileSize > int64(maxSinglePutObjectSize) {
return 0, ErrorResponse{
Code: "NotImplemented",
@@ -82,11 +88,11 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
}
}
// Do not compute MD5 for Google Cloud Storage. Uploads up to 5GiB in size.
return c.putObjectNoChecksum(bucketName, objectName, fileReader, fileSize, contentType, nil)
return c.putObjectNoChecksum(bucketName, objectName, fileReader, fileSize, objMetadata, nil)
}
// NOTE: S3 doesn't allow anonymous multipart requests.
if isAmazonEndpoint(c.endpointURL) && c.anonymous {
if s3utils.IsAmazonEndpoint(c.endpointURL) && c.anonymous {
if fileSize > int64(maxSinglePutObjectSize) {
return 0, ErrorResponse{
Code: "NotImplemented",
@@ -97,15 +103,15 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
}
// Do not compute MD5 for anonymous requests to Amazon
// S3. Uploads up to 5GiB in size.
return c.putObjectNoChecksum(bucketName, objectName, fileReader, fileSize, contentType, nil)
return c.putObjectNoChecksum(bucketName, objectName, fileReader, fileSize, objMetadata, nil)
}
// Small object upload is initiated for uploads for input data size smaller than 5MiB.
if fileSize < minPartSize && fileSize >= 0 {
return c.putObjectSingle(bucketName, objectName, fileReader, fileSize, contentType, nil)
return c.putObjectSingle(bucketName, objectName, fileReader, fileSize, objMetadata, nil)
}
// Upload all large objects as multipart.
n, err = c.putObjectMultipartFromFile(bucketName, objectName, fileReader, fileSize, contentType, nil)
n, err = c.putObjectMultipartFromFile(bucketName, objectName, fileReader, fileSize, objMetadata, nil)
if err != nil {
errResp := ToErrorResponse(err)
// Verify if multipart functionality is not available, if not
@@ -116,7 +122,7 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
return 0, ErrEntityTooLarge(fileSize, maxSinglePutObjectSize, bucketName, objectName)
}
// Fall back to uploading as single PutObject operation.
return c.putObjectSingle(bucketName, objectName, fileReader, fileSize, contentType, nil)
return c.putObjectSingle(bucketName, objectName, fileReader, fileSize, objMetadata, nil)
}
return n, err
}
@@ -131,7 +137,7 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string)
// against MD5SUM of each individual parts. This function also
// effectively utilizes file system capabilities of reading from
// specific sections and not having to create temporary files.
func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileReader io.ReaderAt, fileSize int64, contentType string, progress io.Reader) (int64, error) {
func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileReader io.ReaderAt, fileSize int64, metaData map[string][]string, progress io.Reader) (int64, error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -140,9 +146,8 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
return 0, err
}
// Get upload id for an object, initiates a new multipart request
// if it cannot find any previously partially uploaded object.
uploadID, isNew, err := c.getUploadID(bucketName, objectName, contentType)
// Get the upload id of a previously partially uploaded object or initiate a new multipart upload
uploadID, partsInfo, err := c.getMpartUploadSession(bucketName, objectName, metaData)
if err != nil {
return 0, err
}
@@ -153,19 +158,6 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
// Complete multipart upload.
var complMultipartUpload completeMultipartUpload
// A map of all uploaded parts.
var partsInfo = make(map[int]objectPart)
// If this session is a continuation of a previous session fetch all
// previously uploaded parts info.
if !isNew {
// Fetch previously upload parts and maximum part size.
partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
if err != nil {
return 0, err
}
}
// Calculate the optimal parts info for a given size.
totalPartsCount, partSize, lastPartSize, err := optimalPartInfo(fileSize)
if err != nil {
@@ -178,14 +170,19 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
// Create a channel to communicate which part to upload.
// Buffer this to 10000, the maximum number of parts allowed by S3.
uploadPartsCh := make(chan int, 10000)
uploadPartsCh := make(chan uploadPartReq, 10000)
// Just for readability.
lastPartNumber := totalPartsCount
// Send each part through the partUploadCh to be uploaded.
for p := 1; p <= totalPartsCount; p++ {
uploadPartsCh <- p
part, ok := partsInfo[p]
if ok {
uploadPartsCh <- uploadPartReq{PartNum: p, Part: &part}
} else {
uploadPartsCh <- uploadPartReq{PartNum: p, Part: nil}
}
}
close(uploadPartsCh)
@@ -193,7 +190,7 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
for w := 1; w <= 3; w++ {
go func() {
// Deal with each part as it comes through the channel.
for partNumber := range uploadPartsCh {
for uploadReq := range uploadPartsCh {
// Add hash algorithms that need to be calculated by computeHash()
// In case of a non-v4 signature or https connection, sha256 is not needed.
hashAlgos := make(map[string]hash.Hash)
@@ -203,47 +200,50 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
hashAlgos["sha256"] = sha256.New()
}
// If partNumber was not uploaded we calculate the missing
// part offset and size. For all other part numbers we
// calculate offset based on multiples of partSize.
readOffset := int64(uploadReq.PartNum-1) * partSize
missingPartSize := partSize
// As a special case if partNumber is lastPartNumber, we
// calculate the offset based on the last part size.
if uploadReq.PartNum == lastPartNumber {
readOffset = (fileSize - lastPartSize)
missingPartSize = lastPartSize
}
// Get a section reader on a particular offset.
sectionReader := io.NewSectionReader(fileReader, readOffset, missingPartSize)
var prtSize int64
var err error
prtSize, err = computeHash(hashAlgos, hashSums, sectionReader)
if err != nil {
uploadedPartsCh <- uploadedPartRes{
Error: err,
}
// Exit the goroutine.
return
}
// Create the part to be uploaded.
verifyObjPart := objectPart{
ETag: hex.EncodeToString(hashSums["md5"]),
PartNumber: partNumber,
PartNumber: uploadReq.PartNum,
Size: partSize,
}
// If this is the last part do not give it the full part size.
if partNumber == lastPartNumber {
if uploadReq.PartNum == lastPartNumber {
verifyObjPart.Size = lastPartSize
}
// Verify if part should be uploaded.
if shouldUploadPart(verifyObjPart, partsInfo) {
// If partNumber was not uploaded we calculate the missing
// part offset and size. For all other part numbers we
// calculate offset based on multiples of partSize.
readOffset := int64(partNumber-1) * partSize
missingPartSize := partSize
// As a special case if partNumber is lastPartNumber, we
// calculate the offset based on the last part size.
if partNumber == lastPartNumber {
readOffset = (fileSize - lastPartSize)
missingPartSize = lastPartSize
}
// Get a section reader on a particular offset.
sectionReader := io.NewSectionReader(fileReader, readOffset, missingPartSize)
var prtSize int64
prtSize, err = computeHash(hashAlgos, hashSums, sectionReader)
if err != nil {
uploadedPartsCh <- uploadedPartRes{
Error: err,
}
// Exit the goroutine.
return
}
if shouldUploadPart(verifyObjPart, uploadReq) {
// Proceed to upload the part.
var objPart objectPart
objPart, err = c.uploadPart(bucketName, objectName, uploadID, sectionReader, partNumber, hashSums["md5"], hashSums["sha256"], prtSize)
objPart, err = c.uploadPart(bucketName, objectName, uploadID, sectionReader, uploadReq.PartNum, hashSums["md5"], hashSums["sha256"], prtSize)
if err != nil {
uploadedPartsCh <- uploadedPartRes{
Error: err,
@@ -252,12 +252,13 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
return
}
// Save successfully uploaded part metadata.
partsInfo[partNumber] = objPart
uploadReq.Part = &objPart
}
// Return through the channel the part size.
uploadedPartsCh <- uploadedPartRes{
Size: verifyObjPart.Size,
PartNum: partNumber,
PartNum: uploadReq.PartNum,
Part: uploadReq.Part,
Error: nil,
}
}
@@ -271,8 +272,8 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe
return totalUploadedSize, uploadRes.Error
}
// Retrieve each uploaded part and store it to be completed.
part, ok := partsInfo[uploadRes.PartNum]
if !ok {
part := uploadRes.Part
if part == nil {
return totalUploadedSize, ErrInvalidArgument(fmt.Sprintf("Missing part number %d", uploadRes.PartNum))
}
// Update the total uploaded size.

View File

@@ -45,11 +45,11 @@ import (
// If we exhaust all the known types, code proceeds to use stream as
// is where each part is re-downloaded, checksummed and verified
// before upload.
func (c Client) putObjectMultipart(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
func (c Client) putObjectMultipart(bucketName, objectName string, reader io.Reader, size int64, metaData map[string][]string, progress io.Reader) (n int64, err error) {
if size > 0 && size > minPartSize {
// Verify if reader is *os.File, then use file system functionalities.
if isFile(reader) {
return c.putObjectMultipartFromFile(bucketName, objectName, reader.(*os.File), size, contentType, progress)
return c.putObjectMultipartFromFile(bucketName, objectName, reader.(*os.File), size, metaData, progress)
}
// Verify if reader is *minio.Object or io.ReaderAt.
// NOTE: Verification of object is kept for a specific purpose
@@ -58,17 +58,17 @@ func (c Client) putObjectMultipart(bucketName, objectName string, reader io.Read
// and such a functionality is used in the subsequent code
// path.
if isObject(reader) || isReadAt(reader) {
return c.putObjectMultipartFromReadAt(bucketName, objectName, reader.(io.ReaderAt), size, contentType, progress)
return c.putObjectMultipartFromReadAt(bucketName, objectName, reader.(io.ReaderAt), size, metaData, progress)
}
}
// For any other data size and reader type we do generic multipart
// approach by staging data in temporary files and uploading them.
return c.putObjectMultipartStream(bucketName, objectName, reader, size, contentType, progress)
return c.putObjectMultipartStream(bucketName, objectName, reader, size, metaData, progress)
}
// putObjectStream uploads files bigger than 5MiB, and also supports
// putObjectStream uploads files bigger than 64MiB, and also supports
// special case where size is unknown i.e '-1'.
func (c Client) putObjectMultipartStream(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
func (c Client) putObjectMultipartStream(bucketName, objectName string, reader io.Reader, size int64, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -83,27 +83,12 @@ func (c Client) putObjectMultipartStream(bucketName, objectName string, reader i
// Complete multipart upload.
var complMultipartUpload completeMultipartUpload
// A map of all previously uploaded parts.
var partsInfo = make(map[int]objectPart)
// getUploadID for an object, initiates a new multipart request
// if it cannot find any previously partially uploaded object.
uploadID, isNew, err := c.getUploadID(bucketName, objectName, contentType)
// Get the upload id of a previously partially uploaded object or initiate a new multipart upload
uploadID, partsInfo, err := c.getMpartUploadSession(bucketName, objectName, metaData)
if err != nil {
return 0, err
}
// If This session is a continuation of a previous session fetch all
// previously uploaded parts info and as a special case only fetch partsInfo
// for only known upload size.
if !isNew {
// Fetch previously uploaded parts and maximum part size.
partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
if err != nil {
return 0, err
}
}
// Calculate the optimal parts info for a given size.
totalPartsCount, partSize, _, err := optimalPartInfo(size)
if err != nil {
@@ -139,12 +124,14 @@ func (c Client) putObjectMultipartStream(bucketName, objectName string, reader i
// as we read from the source.
reader = newHook(tmpBuffer, progress)
part, ok := partsInfo[partNumber]
// Verify if part should be uploaded.
if shouldUploadPart(objectPart{
if !ok || shouldUploadPart(objectPart{
ETag: hex.EncodeToString(hashSums["md5"]),
PartNumber: partNumber,
Size: prtSize,
}, partsInfo) {
}, uploadPartReq{PartNum: partNumber, Part: &part}) {
// Proceed to upload the part.
var objPart objectPart
objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, hashSums["md5"], hashSums["sha256"], prtSize)
@@ -212,7 +199,7 @@ func (c Client) putObjectMultipartStream(bucketName, objectName string, reader i
}
// initiateMultipartUpload - Initiates a multipart upload and returns an upload ID.
func (c Client) initiateMultipartUpload(bucketName, objectName, contentType string) (initiateMultipartUploadResult, error) {
func (c Client) initiateMultipartUpload(bucketName, objectName string, metaData map[string][]string) (initiateMultipartUploadResult, error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return initiateMultipartUploadResult{}, err
@@ -225,13 +212,18 @@ func (c Client) initiateMultipartUpload(bucketName, objectName, contentType stri
urlValues := make(url.Values)
urlValues.Set("uploads", "")
if contentType == "" {
contentType = "application/octet-stream"
}
// Set ContentType header.
customHeader := make(http.Header)
customHeader.Set("Content-Type", contentType)
for k, v := range metaData {
if len(v) > 0 {
customHeader.Set(k, v[0])
}
}
// Set a default content-type header if the latter is not provided
if v, ok := metaData["Content-Type"]; !ok || len(v) == 0 {
customHeader.Set("Content-Type", "application/octet-stream")
}
reqMetadata := requestMetadata{
bucketName: bucketName,

View File

@@ -19,10 +19,19 @@ package minio
import (
"io"
"strings"
"github.com/minio/minio-go/pkg/s3utils"
)
// PutObjectWithProgress - With progress.
// PutObjectWithProgress - with progress.
func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.Reader, contentType string, progress io.Reader) (n int64, err error) {
metaData := make(map[string][]string)
metaData["Content-Type"] = []string{contentType}
return c.PutObjectWithMetadata(bucketName, objectName, reader, metaData, progress)
}
// PutObjectWithMetadata - with metadata.
func (c Client) PutObjectWithMetadata(bucketName, objectName string, reader io.Reader, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -50,7 +59,7 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R
// NOTE: Google Cloud Storage does not implement Amazon S3 Compatible multipart PUT.
// So we fall back to single PUT operation with the maximum limit of 5GiB.
if isGoogleEndpoint(c.endpointURL) {
if s3utils.IsGoogleEndpoint(c.endpointURL) {
if size <= -1 {
return 0, ErrorResponse{
Code: "NotImplemented",
@@ -63,11 +72,11 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R
return 0, ErrEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
}
// Do not compute MD5 for Google Cloud Storage. Uploads up to 5GiB in size.
return c.putObjectNoChecksum(bucketName, objectName, reader, size, contentType, progress)
return c.putObjectNoChecksum(bucketName, objectName, reader, size, metaData, progress)
}
// NOTE: S3 doesn't allow anonymous multipart requests.
if isAmazonEndpoint(c.endpointURL) && c.anonymous {
if s3utils.IsAmazonEndpoint(c.endpointURL) && c.anonymous {
if size <= -1 {
return 0, ErrorResponse{
Code: "NotImplemented",
@@ -81,15 +90,15 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R
}
// Do not compute MD5 for anonymous requests to Amazon
// S3. Uploads up to 5GiB in size.
return c.putObjectNoChecksum(bucketName, objectName, reader, size, contentType, progress)
return c.putObjectNoChecksum(bucketName, objectName, reader, size, metaData, progress)
}
// putSmall object.
if size < minPartSize && size >= 0 {
return c.putObjectSingle(bucketName, objectName, reader, size, contentType, progress)
return c.putObjectSingle(bucketName, objectName, reader, size, metaData, progress)
}
// For all sizes greater than 5MiB do multipart.
n, err = c.putObjectMultipart(bucketName, objectName, reader, size, contentType, progress)
n, err = c.putObjectMultipart(bucketName, objectName, reader, size, metaData, progress)
if err != nil {
errResp := ToErrorResponse(err)
// Verify if multipart functionality is not available, if not
@@ -100,7 +109,7 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R
return 0, ErrEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
}
// Fall back to uploading as single PutObject operation.
return c.putObjectSingle(bucketName, objectName, reader, size, contentType, progress)
return c.putObjectSingle(bucketName, objectName, reader, size, metaData, progress)
}
return n, err
}

View File

@@ -32,17 +32,22 @@ type uploadedPartRes struct {
Error error // Any error encountered while uploading the part.
PartNum int // Number of the part uploaded.
Size int64 // Size of the part uploaded.
Part *objectPart
}
type uploadPartReq struct {
PartNum int // Number of the part uploaded.
Part *objectPart // Size of the part uploaded.
}
// shouldUploadPartReadAt - verify if part should be uploaded.
func shouldUploadPartReadAt(objPart objectPart, objectParts map[int]objectPart) bool {
func shouldUploadPartReadAt(objPart objectPart, uploadReq uploadPartReq) bool {
// If part not found part should be uploaded.
uploadedPart, found := objectParts[objPart.PartNumber]
if !found {
if uploadReq.Part == nil {
return true
}
// if size mismatches part should be uploaded.
if uploadedPart.Size != objPart.Size {
if uploadReq.Part.Size != objPart.Size {
return true
}
return false
@@ -58,7 +63,7 @@ func shouldUploadPartReadAt(objPart objectPart, objectParts map[int]objectPart)
// temporary files for staging all the data, these temporary files are
// cleaned automatically when the caller i.e http client closes the
// stream after uploading all the contents successfully.
func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, reader io.ReaderAt, size int64, contentType string, progress io.Reader) (n int64, err error) {
func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, reader io.ReaderAt, size int64, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -67,9 +72,8 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
return 0, err
}
// Get upload id for an object, initiates a new multipart request
// if it cannot find any previously partially uploaded object.
uploadID, isNew, err := c.getUploadID(bucketName, objectName, contentType)
// Get the upload id of a previously partially uploaded object or initiate a new multipart upload
uploadID, partsInfo, err := c.getMpartUploadSession(bucketName, objectName, metaData)
if err != nil {
return 0, err
}
@@ -80,17 +84,6 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
// Complete multipart upload.
var complMultipartUpload completeMultipartUpload
// A map of all uploaded parts.
var partsInfo = make(map[int]objectPart)
// Fetch all parts info previously uploaded.
if !isNew {
partsInfo, err = c.listObjectParts(bucketName, objectName, uploadID)
if err != nil {
return 0, err
}
}
// Calculate the optimal parts info for a given size.
totalPartsCount, partSize, lastPartSize, err := optimalPartInfo(size)
if err != nil {
@@ -103,7 +96,7 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
// Declare a channel that sends the next part number to be uploaded.
// Buffered to 10000 because thats the maximum number of parts allowed
// by S3.
uploadPartsCh := make(chan int, 10000)
uploadPartsCh := make(chan uploadPartReq, 10000)
// Declare a channel that sends back the response of a part upload.
// Buffered to 10000 because thats the maximum number of parts allowed
@@ -112,7 +105,12 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
// Send each part number to the channel to be processed.
for p := 1; p <= totalPartsCount; p++ {
uploadPartsCh <- p
part, ok := partsInfo[p]
if ok {
uploadPartsCh <- uploadPartReq{PartNum: p, Part: &part}
} else {
uploadPartsCh <- uploadPartReq{PartNum: p, Part: nil}
}
}
close(uploadPartsCh)
@@ -123,64 +121,65 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
readAtBuffer := make([]byte, optimalReadBufferSize)
// Each worker will draw from the part channel and upload in parallel.
for partNumber := range uploadPartsCh {
for uploadReq := range uploadPartsCh {
// Declare a new tmpBuffer.
tmpBuffer := new(bytes.Buffer)
// If partNumber was not uploaded we calculate the missing
// part offset and size. For all other part numbers we
// calculate offset based on multiples of partSize.
readOffset := int64(uploadReq.PartNum-1) * partSize
missingPartSize := partSize
// As a special case if partNumber is lastPartNumber, we
// calculate the offset based on the last part size.
if uploadReq.PartNum == lastPartNumber {
readOffset = (size - lastPartSize)
missingPartSize = lastPartSize
}
// Get a section reader on a particular offset.
sectionReader := io.NewSectionReader(reader, readOffset, missingPartSize)
// Choose the needed hash algorithms to be calculated by hashCopyBuffer.
// Sha256 is avoided in non-v4 signature requests or HTTPS connections
hashSums := make(map[string][]byte)
hashAlgos := make(map[string]hash.Hash)
hashAlgos["md5"] = md5.New()
if c.signature.isV4() && !c.secure {
hashAlgos["sha256"] = sha256.New()
}
var prtSize int64
var err error
prtSize, err = hashCopyBuffer(hashAlgos, hashSums, tmpBuffer, sectionReader, readAtBuffer)
if err != nil {
// Send the error back through the channel.
uploadedPartsCh <- uploadedPartRes{
Size: 0,
Error: err,
}
// Exit the goroutine.
return
}
// Verify object if its uploaded.
verifyObjPart := objectPart{
PartNumber: partNumber,
PartNumber: uploadReq.PartNum,
Size: partSize,
}
// Special case if we see a last part number, save last part
// size as the proper part size.
if partNumber == lastPartNumber {
if uploadReq.PartNum == lastPartNumber {
verifyObjPart.Size = lastPartSize
}
// Only upload the necessary parts. Otherwise return size through channel
// to update any progress bar.
if shouldUploadPartReadAt(verifyObjPart, partsInfo) {
// If partNumber was not uploaded we calculate the missing
// part offset and size. For all other part numbers we
// calculate offset based on multiples of partSize.
readOffset := int64(partNumber-1) * partSize
missingPartSize := partSize
// As a special case if partNumber is lastPartNumber, we
// calculate the offset based on the last part size.
if partNumber == lastPartNumber {
readOffset = (size - lastPartSize)
missingPartSize = lastPartSize
}
// Get a section reader on a particular offset.
sectionReader := io.NewSectionReader(reader, readOffset, missingPartSize)
// Choose the needed hash algorithms to be calculated by hashCopyBuffer.
// Sha256 is avoided in non-v4 signature requests or HTTPS connections
hashSums := make(map[string][]byte)
hashAlgos := make(map[string]hash.Hash)
hashAlgos["md5"] = md5.New()
if c.signature.isV4() && !c.secure {
hashAlgos["sha256"] = sha256.New()
}
var prtSize int64
prtSize, err = hashCopyBuffer(hashAlgos, hashSums, tmpBuffer, sectionReader, readAtBuffer)
if err != nil {
// Send the error back through the channel.
uploadedPartsCh <- uploadedPartRes{
Size: 0,
Error: err,
}
// Exit the goroutine.
return
}
if shouldUploadPartReadAt(verifyObjPart, uploadReq) {
// Proceed to upload the part.
var objPart objectPart
objPart, err = c.uploadPart(bucketName, objectName, uploadID, tmpBuffer, partNumber, hashSums["md5"], hashSums["sha256"], prtSize)
objPart, err = c.uploadPart(bucketName, objectName, uploadID, tmpBuffer, uploadReq.PartNum, hashSums["md5"], hashSums["sha256"], prtSize)
if err != nil {
uploadedPartsCh <- uploadedPartRes{
Size: 0,
@@ -190,12 +189,13 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
return
}
// Save successfully uploaded part metadata.
partsInfo[partNumber] = objPart
uploadReq.Part = &objPart
}
// Send successful part info through the channel.
uploadedPartsCh <- uploadedPartRes{
Size: verifyObjPart.Size,
PartNum: partNumber,
PartNum: uploadReq.PartNum,
Part: uploadReq.Part,
Error: nil,
}
}
@@ -210,8 +210,9 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read
return totalUploadedSize, uploadRes.Error
}
// Retrieve each uploaded part and store it to be completed.
part, ok := partsInfo[uploadRes.PartNum]
if !ok {
// part, ok := partsInfo[uploadRes.PartNum]
part := uploadRes.Part
if part == nil {
return 0, ErrInvalidArgument(fmt.Sprintf("Missing part number %d", uploadRes.PartNum))
}
// Update the totalUploadedSize.

View File

@@ -103,11 +103,10 @@ func getReaderSize(reader io.Reader) (size int64, err error) {
// implement Seekable calls. Ignore them and treat
// them like a stream with unknown length.
switch st.Name() {
case "stdin":
fallthrough
case "stdout":
fallthrough
case "stderr":
case "stdin", "stdout", "stderr":
return
// Ignore read/write stream of os.Pipe() which have unknown length too.
case "|0", "|1":
return
}
size = st.Size()
@@ -151,7 +150,7 @@ func (c Client) PutObject(bucketName, objectName string, reader io.Reader, conte
// putObjectNoChecksum special function used Google Cloud Storage. This special function
// is used for Google Cloud Storage since Google's multipart API is not S3 compatible.
func (c Client) putObjectNoChecksum(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
func (c Client) putObjectNoChecksum(bucketName, objectName string, reader io.Reader, size int64, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -169,7 +168,7 @@ func (c Client) putObjectNoChecksum(bucketName, objectName string, reader io.Rea
// This function does not calculate sha256 and md5sum for payload.
// Execute put object.
st, err := c.putObjectDo(bucketName, objectName, readSeeker, nil, nil, size, contentType)
st, err := c.putObjectDo(bucketName, objectName, readSeeker, nil, nil, size, metaData)
if err != nil {
return 0, err
}
@@ -181,7 +180,7 @@ func (c Client) putObjectNoChecksum(bucketName, objectName string, reader io.Rea
// putObjectSingle is a special function for uploading single put object request.
// This special function is used as a fallback when multipart upload fails.
func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader, size int64, contentType string, progress io.Reader) (n int64, err error) {
func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader, size int64, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return 0, err
@@ -237,7 +236,7 @@ func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader,
}
}
// Execute put object.
st, err := c.putObjectDo(bucketName, objectName, reader, hashSums["md5"], hashSums["sha256"], size, contentType)
st, err := c.putObjectDo(bucketName, objectName, reader, hashSums["md5"], hashSums["sha256"], size, metaData)
if err != nil {
return 0, err
}
@@ -255,7 +254,7 @@ func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader,
// putObjectDo - executes the put object http operation.
// NOTE: You must have WRITE permissions on a bucket to add an object to it.
func (c Client) putObjectDo(bucketName, objectName string, reader io.Reader, md5Sum []byte, sha256Sum []byte, size int64, contentType string) (ObjectInfo, error) {
func (c Client) putObjectDo(bucketName, objectName string, reader io.Reader, md5Sum []byte, sha256Sum []byte, size int64, metaData map[string][]string) (ObjectInfo, error) {
// Input validation.
if err := isValidBucketName(bucketName); err != nil {
return ObjectInfo{}, err
@@ -272,13 +271,20 @@ func (c Client) putObjectDo(bucketName, objectName string, reader io.Reader, md5
return ObjectInfo{}, ErrEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
}
if strings.TrimSpace(contentType) == "" {
contentType = "application/octet-stream"
}
// Set headers.
customHeader := make(http.Header)
customHeader.Set("Content-Type", contentType)
// Set metadata to headers
for k, v := range metaData {
if len(v) > 0 {
customHeader.Set(k, v[0])
}
}
// If Content-Type is not provided, set the default application/octet-stream one
if v, ok := metaData["Content-Type"]; !ok || len(v) == 0 {
customHeader.Set("Content-Type", "application/octet-stream")
}
// Populate request metadata.
reqMetadata := requestMetadata{
@@ -303,13 +309,13 @@ func (c Client) putObjectDo(bucketName, objectName string, reader io.Reader, md5
}
}
var metadata ObjectInfo
var objInfo ObjectInfo
// Trim off the odd double quotes from ETag in the beginning and end.
metadata.ETag = strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
metadata.ETag = strings.TrimSuffix(metadata.ETag, "\"")
objInfo.ETag = strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
objInfo.ETag = strings.TrimSuffix(objInfo.ETag, "\"")
// A success here means data was written to server successfully.
metadata.Size = size
objInfo.Size = size
// Return here.
return metadata, nil
return objInfo, nil
}

View File

@@ -71,6 +71,13 @@ func (c Client) RemoveObject(bucketName, objectName string) error {
if err != nil {
return err
}
if resp != nil {
// if some unexpected error happened and max retry is reached, we want to let client know
if resp.StatusCode != http.StatusNoContent {
return httpRespToErrorResponse(resp, bucketName, objectName)
}
}
// DeleteObject always responds with http '204' even for
// objects which do not exist. So no need to handle them
// specifically.
@@ -164,6 +171,10 @@ func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan
break
}
}
if count == 0 {
// Multi Objects Delete API doesn't accept empty object list, quit immediatly
break
}
if count < maxEntries {
// We didn't have 1000 entries, so this is the last batch
finish = true

Some files were not shown because too many files have changed in this diff Show More