mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Updating server dependancies (#5249)
This commit is contained in:
committed by
Harrison Healey
parent
ca3211bc04
commit
701d1ab638
@@ -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
54
glide.lock
generated
@@ -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: []
|
||||
|
||||
27
glide.yaml
27
glide.yaml
@@ -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
|
||||
|
||||
14
vendor/github.com/NYTimes/gziphandler/gzip.go
generated
vendored
14
vendor/github.com/NYTimes/gziphandler/gzip.go
generated
vendored
@@ -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 {
|
||||
|
||||
30
vendor/github.com/disintegration/imaging/effects.go
generated
vendored
30
vendor/github.com/disintegration/imaging/effects.go
generated
vendored
@@ -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
|
||||
|
||||
14
vendor/github.com/disintegration/imaging/effects_test.go
generated
vendored
14
vendor/github.com/disintegration/imaging/effects_test.go
generated
vendored
@@ -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
43
vendor/github.com/disintegration/imaging/histogram.go
generated
vendored
Normal 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
|
||||
}
|
||||
42
vendor/github.com/disintegration/imaging/histogram_test.go
generated
vendored
Normal file
42
vendor/github.com/disintegration/imaging/histogram_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/disintegration/imaging/resize.go
generated
vendored
38
vendor/github.com/disintegration/imaging/resize.go
generated
vendored
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
vendor/github.com/disintegration/imaging/resize_test.go
generated
vendored
18
vendor/github.com/disintegration/imaging/resize_test.go
generated
vendored
@@ -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},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
9
vendor/github.com/go-ldap/ldap/.travis.yml
generated
vendored
9
vendor/github.com/go-ldap/ldap/.travis.yml
generated
vendored
@@ -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
|
||||
|
||||
43
vendor/github.com/go-ldap/ldap/LICENSE
generated
vendored
43
vendor/github.com/go-ldap/ldap/LICENSE
generated
vendored
@@ -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.
|
||||
|
||||
12
vendor/github.com/go-ldap/ldap/control.go
generated
vendored
12
vendor/github.com/go-ldap/ldap/control.go
generated
vendored
@@ -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 {
|
||||
|
||||
39
vendor/github.com/go-ldap/ldap/control_test.go
generated
vendored
39
vendor/github.com/go-ldap/ldap/control_test.go
generated
vendored
@@ -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
96
vendor/github.com/go-ldap/ldap/dn.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
139
vendor/github.com/go-ldap/ldap/dn_test.go
generated
vendored
139
vendor/github.com/go-ldap/ldap/dn_test.go
generated
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
vendor/github.com/go-ldap/ldap/ldap.go
generated
vendored
59
vendor/github.com/go-ldap/ldap/ldap.go
generated
vendored
@@ -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 {
|
||||
|
||||
21
vendor/github.com/go-sql-driver/mysql/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
21
vendor/github.com/go-sql-driver/mysql/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal 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
|
||||
9
vendor/github.com/go-sql-driver/mysql/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
9
vendor/github.com/go-sql-driver/mysql/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal 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
|
||||
8
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
8
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
@@ -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:
|
||||
|
||||
21
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
21
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
@@ -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.
|
||||
|
||||
47
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
47
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
@@ -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:
|
||||
|
||||
17
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
17
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
@@ -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.
|
||||
|
||||
143
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
143
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
@@ -4,10 +4,6 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
|
||||
|
||||

|
||||
|
||||
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
|
||||
|
||||
[](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.
|
||||
|
||||
|
||||
40
vendor/github.com/go-sql-driver/mysql/benchmark_test.go
generated
vendored
40
vendor/github.com/go-sql-driver/mysql/benchmark_test.go
generated
vendored
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
55
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
16
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
@@ -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,
|
||||
}
|
||||
|
||||
279
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
279
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
@@ -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
|
||||
|
||||
67
vendor/github.com/go-sql-driver/mysql/connection_test.go
generated
vendored
Normal file
67
vendor/github.com/go-sql-driver/mysql/connection_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
37
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
37
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
@@ -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
|
||||
)
|
||||
|
||||
117
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
117
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
@@ -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{})
|
||||
}
|
||||
|
||||
960
vendor/github.com/go-sql-driver/mysql/driver_test.go
generated
vendored
960
vendor/github.com/go-sql-driver/mysql/driver_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
548
vendor/github.com/go-sql-driver/mysql/dsn.go
generated
vendored
Normal file
548
vendor/github.com/go-sql-driver/mysql/dsn.go
generated
vendored
Normal 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&...¶mN=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¶mN=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&...¶mN=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
231
vendor/github.com/go-sql-driver/mysql/dsn_test.go
generated
vendored
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
23
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
@@ -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 {
|
||||
|
||||
56
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
56
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
453
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
453
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
@@ -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
282
vendor/github.com/go-sql-driver/mysql/packets_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
50
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
50
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
45
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
45
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
@@ -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())
|
||||
}
|
||||
|
||||
572
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
572
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
@@ -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¶mN=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&...¶mN=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]
|
||||
}
|
||||
|
||||
143
vendor/github.com/go-sql-driver/mysql/utils_test.go
generated
vendored
143
vendor/github.com/go-sql-driver/mysql/utils_test.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
3
vendor/github.com/goamz/goamz/s3/s3.go
generated
vendored
3
vendor/github.com/goamz/goamz/s3/s3.go
generated
vendored
@@ -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)
|
||||
|
||||
5
vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
5
vendor/github.com/golang/freetype/truetype/glyph.go
generated
vendored
@@ -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])
|
||||
|
||||
|
||||
15
vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
15
vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
@@ -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 {
|
||||
|
||||
44
vendor/github.com/gorilla/mux/README.md
generated
vendored
44
vendor/github.com/gorilla/mux/README.md
generated
vendored
@@ -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
|
||||
|
||||
5
vendor/github.com/gorilla/mux/doc.go
generated
vendored
5
vendor/github.com/gorilla/mux/doc.go
generated
vendored
@@ -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.
|
||||
|
||||
|
||||
10
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
10
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
@@ -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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
7
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
7
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
@@ -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,
|
||||
|
||||
26
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
26
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
@@ -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")
|
||||
}
|
||||
|
||||
42
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
42
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@@ -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
14
vendor/github.com/lib/pq/.travis.yml
generated
vendored
@@ -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
45
vendor/github.com/lib/pq/array.go
generated
vendored
@@ -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++
|
||||
|
||||
164
vendor/github.com/lib/pq/array_test.go
generated
vendored
164
vendor/github.com/lib/pq/array_test.go
generated
vendored
@@ -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
189
vendor/github.com/lib/pq/conn.go
generated
vendored
@@ -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
92
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal 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
27
vendor/github.com/lib/pq/copy.go
generated
vendored
@@ -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
8
vendor/github.com/lib/pq/encode.go
generated
vendored
@@ -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
|
||||
|
||||
46
vendor/github.com/lib/pq/encode_test.go
generated
vendored
46
vendor/github.com/lib/pq/encode_test.go
generated
vendored
@@ -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()
|
||||
|
||||
99
vendor/github.com/lib/pq/go18_test.go
generated
vendored
99
vendor/github.com/lib/pq/go18_test.go
generated
vendored
@@ -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
175
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal 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
14
vendor/github.com/lib/pq/ssl_go1.7.go
generated
vendored
Normal 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
16
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal 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
8
vendor/github.com/lib/pq/ssl_renegotiation.go
generated
vendored
Normal 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
9
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal 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
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal 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
46
vendor/github.com/lib/pq/uuid_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
8
vendor/github.com/miekg/dns/.travis.yml
generated
vendored
8
vendor/github.com/miekg/dns/.travis.yml
generated
vendored
@@ -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=.
|
||||
|
||||
5
vendor/github.com/miekg/dns/README.md
generated
vendored
5
vendor/github.com/miekg/dns/README.md
generated
vendored
@@ -1,4 +1,5 @@
|
||||
[](https://travis-ci.org/miekg/dns) [](https://godoc.org/github.com/miekg/dns)
|
||||
[](https://travis-ci.org/miekg/dns)
|
||||
[](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
|
||||
|
||||
|
||||
4
vendor/github.com/miekg/dns/defaults.go
generated
vendored
4
vendor/github.com/miekg/dns/defaults.go
generated
vendored
@@ -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)
|
||||
|
||||
2
vendor/github.com/miekg/dns/edns.go
generated
vendored
2
vendor/github.com/miekg/dns/edns.go
generated
vendored
@@ -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
29
vendor/github.com/miekg/dns/msg.go
generated
vendored
@@ -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))
|
||||
|
||||
|
||||
4
vendor/github.com/miekg/dns/msg_generate.go
generated
vendored
4
vendor/github.com/miekg/dns/msg_generate.go
generated
vendored
@@ -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"`:
|
||||
|
||||
2
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
2
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
@@ -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
|
||||
|
||||
19
vendor/github.com/miekg/dns/parse_test.go
generated
vendored
19
vendor/github.com/miekg/dns/parse_test.go
generated
vendored
@@ -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.",
|
||||
|
||||
2
vendor/github.com/miekg/dns/sanitize_test.go
generated
vendored
2
vendor/github.com/miekg/dns/sanitize_test.go
generated
vendored
@@ -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)
|
||||
|
||||
112
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
112
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/miekg/dns/server.go
generated
vendored
2
vendor/github.com/miekg/dns/server.go
generated
vendored
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
40
vendor/github.com/miekg/dns/server_test.go
generated
vendored
40
vendor/github.com/miekg/dns/server_test.go
generated
vendored
@@ -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
19
vendor/github.com/miekg/dns/types.go
generated
vendored
@@ -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 {
|
||||
|
||||
9
vendor/github.com/miekg/dns/udp_linux.go
generated
vendored
9
vendor/github.com/miekg/dns/udp_linux.go
generated
vendored
@@ -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
34
vendor/github.com/miekg/dns/zmsg.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
6
vendor/github.com/minio/minio-go/.travis.yml
generated
vendored
6
vendor/github.com/minio/minio-go/.travis.yml
generated
vendored
@@ -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=
|
||||
|
||||
28
vendor/github.com/minio/minio-go/README.md
generated
vendored
28
vendor/github.com/minio/minio-go/README.md
generated
vendored
@@ -1,5 +1,5 @@
|
||||
# Minio Golang Library for Amazon S3 Compatible Cloud Storage [](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 [](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
|
||||
|
||||
9
vendor/github.com/minio/minio-go/api-datatypes.go
generated
vendored
9
vendor/github.com/minio/minio-go/api-datatypes.go
generated
vendored
@@ -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"`
|
||||
|
||||
4
vendor/github.com/minio/minio-go/api-get-object.go
generated
vendored
4
vendor/github.com/minio/minio-go/api-get-object.go
generated
vendored
@@ -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")
|
||||
|
||||
4
vendor/github.com/minio/minio-go/api-get-policy.go
generated
vendored
4
vendor/github.com/minio/minio-go/api-get-policy.go
generated
vendored
@@ -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.
|
||||
|
||||
1
vendor/github.com/minio/minio-go/api-list.go
generated
vendored
1
vendor/github.com/minio/minio-go/api-list.go
generated
vendored
@@ -482,6 +482,7 @@ func (c Client) listIncompleteUploads(bucketName, objectPrefix string, recursive
|
||||
objectMultipartStatCh <- ObjectMultipartInfo{
|
||||
Err: err,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
select {
|
||||
|
||||
29
vendor/github.com/minio/minio-go/api-notification.go
generated
vendored
29
vendor/github.com/minio/minio-go/api-notification.go
generated
vendored
@@ -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(), ¬ificationInfo); 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)
|
||||
|
||||
11
vendor/github.com/minio/minio-go/api-presigned.go
generated
vendored
11
vendor/github.com/minio/minio-go/api-presigned.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
13
vendor/github.com/minio/minio-go/api-put-bucket.go
generated
vendored
13
vendor/github.com/minio/minio-go/api-put-bucket.go
generated
vendored
@@ -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.
|
||||
|
||||
17
vendor/github.com/minio/minio-go/api-put-bucket_test.go
generated
vendored
17
vendor/github.com/minio/minio-go/api-put-bucket_test.go
generated
vendored
@@ -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.
|
||||
|
||||
76
vendor/github.com/minio/minio-go/api-put-object-common.go
generated
vendored
76
vendor/github.com/minio/minio-go/api-put-object-common.go
generated
vendored
@@ -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.
|
||||
|
||||
8
vendor/github.com/minio/minio-go/api-put-object-copy.go
generated
vendored
8
vendor/github.com/minio/minio-go/api-put-object-copy.go
generated
vendored
@@ -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{
|
||||
|
||||
121
vendor/github.com/minio/minio-go/api-put-object-file.go
generated
vendored
121
vendor/github.com/minio/minio-go/api-put-object-file.go
generated
vendored
@@ -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.
|
||||
|
||||
54
vendor/github.com/minio/minio-go/api-put-object-multipart.go
generated
vendored
54
vendor/github.com/minio/minio-go/api-put-object-multipart.go
generated
vendored
@@ -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,
|
||||
|
||||
25
vendor/github.com/minio/minio-go/api-put-object-progress.go
generated
vendored
25
vendor/github.com/minio/minio-go/api-put-object-progress.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
135
vendor/github.com/minio/minio-go/api-put-object-readat.go
generated
vendored
135
vendor/github.com/minio/minio-go/api-put-object-readat.go
generated
vendored
@@ -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.
|
||||
|
||||
46
vendor/github.com/minio/minio-go/api-put-object.go
generated
vendored
46
vendor/github.com/minio/minio-go/api-put-object.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
11
vendor/github.com/minio/minio-go/api-remove.go
generated
vendored
11
vendor/github.com/minio/minio-go/api-remove.go
generated
vendored
@@ -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
Reference in New Issue
Block a user