TUN-2748: Insecure randomness vulnerability in github.com/miekg/dns
This commit is contained in:
parent
7b81cf8aa6
commit
6624a24040
6
go.mod
6
go.mod
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0
|
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||||
github.com/google/certificate-transparency-go v1.1.0
|
github.com/google/certificate-transparency-go v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gorilla/mux v1.7.3
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
|
@ -38,7 +38,7 @@ require (
|
||||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.11.0
|
github.com/mattn/go-sqlite3 v1.11.0
|
||||||
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b // indirect
|
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b // indirect
|
||||||
github.com/miekg/dns v1.1.8
|
github.com/miekg/dns v1.1.27
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/opentracing/opentracing-go v1.1.0 // indirect
|
github.com/opentracing/opentracing-go v1.1.0 // indirect
|
||||||
github.com/philhofer/fwd v1.0.0 // indirect
|
github.com/philhofer/fwd v1.0.0 // indirect
|
||||||
|
@ -53,7 +53,7 @@ require (
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/tinylib/msgp v1.1.0 // indirect
|
github.com/tinylib/msgp v1.1.0 // indirect
|
||||||
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0
|
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0
|
||||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954
|
golang.org/x/net v0.0.0-20191007182048-72f939374954
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -215,6 +215,8 @@ github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b h1:/BbY4n99iMazlr2igip
|
||||||
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
||||||
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
||||||
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||||
|
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
@ -319,12 +321,15 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -340,6 +345,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
|
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
|
@ -366,6 +372,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -390,13 +397,16 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
|
||||||
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
|
|
@ -56,7 +56,7 @@ Other supported formats are listed below.
|
||||||
* `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host.
|
* `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host.
|
||||||
* `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port.
|
* `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port.
|
||||||
* `Workstation ID` - The workstation name (default is the host name)
|
* `Workstation ID` - The workstation name (default is the host name)
|
||||||
* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener.
|
* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. The `database` must be specified when connecting with `Application Intent` set to `ReadOnly`.
|
||||||
|
|
||||||
### The connection string can be specified in one of three formats:
|
### The connection string can be specified in one of three formats:
|
||||||
|
|
||||||
|
@ -187,6 +187,19 @@ _, err := db.ExecContext(ctx, "theproc", &rs)
|
||||||
log.Printf("status=%d", rs)
|
log.Printf("status=%d", rs)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
var rs mssql.ReturnStatus
|
||||||
|
_, err := db.QueryContext(ctx, "theproc", &rs)
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&val)
|
||||||
|
}
|
||||||
|
log.Printf("status=%d", rs)
|
||||||
|
```
|
||||||
|
|
||||||
|
Limitation: ReturnStatus cannot be retrieved using `QueryRow`.
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
The `sqlserver` driver uses normal MS SQL Server syntax and expects parameters in
|
The `sqlserver` driver uses normal MS SQL Server syntax and expects parameters in
|
||||||
|
@ -207,9 +220,9 @@ are supported:
|
||||||
* time.Time -> datetimeoffset or datetime (TDS version dependent)
|
* time.Time -> datetimeoffset or datetime (TDS version dependent)
|
||||||
* mssql.DateTime1 -> datetime
|
* mssql.DateTime1 -> datetime
|
||||||
* mssql.DateTimeOffset -> datetimeoffset
|
* mssql.DateTimeOffset -> datetimeoffset
|
||||||
* "cloud.google.com/go/civil".Date -> date
|
* "github.com/golang-sql/civil".Date -> date
|
||||||
* "cloud.google.com/go/civil".DateTime -> datetime2
|
* "github.com/golang-sql/civil".DateTime -> datetime2
|
||||||
* "cloud.google.com/go/civil".Time -> time
|
* "github.com/golang-sql/civil".Time -> time
|
||||||
* mssql.TVP -> Table Value Parameter (TDS version dependent)
|
* mssql.TVP -> Table Value Parameter (TDS version dependent)
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
|
@ -10,7 +10,7 @@ environment:
|
||||||
SQLUSER: sa
|
SQLUSER: sa
|
||||||
SQLPASSWORD: Password12!
|
SQLPASSWORD: Password12!
|
||||||
DATABASE: test
|
DATABASE: test
|
||||||
GOVERSION: 110
|
GOVERSION: 111
|
||||||
matrix:
|
matrix:
|
||||||
- GOVERSION: 18
|
- GOVERSION: 18
|
||||||
SQLINSTANCE: SQL2016
|
SQLINSTANCE: SQL2016
|
||||||
|
@ -18,6 +18,8 @@ environment:
|
||||||
SQLINSTANCE: SQL2016
|
SQLINSTANCE: SQL2016
|
||||||
- GOVERSION: 110
|
- GOVERSION: 110
|
||||||
SQLINSTANCE: SQL2016
|
SQLINSTANCE: SQL2016
|
||||||
|
- GOVERSION: 111
|
||||||
|
SQLINSTANCE: SQL2016
|
||||||
- SQLINSTANCE: SQL2014
|
- SQLINSTANCE: SQL2014
|
||||||
- SQLINSTANCE: SQL2012SP1
|
- SQLINSTANCE: SQL2012SP1
|
||||||
- SQLINSTANCE: SQL2008R2SP2
|
- SQLINSTANCE: SQL2008R2SP2
|
||||||
|
@ -27,7 +29,7 @@ install:
|
||||||
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
|
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
|
||||||
- go version
|
- go version
|
||||||
- go env
|
- go env
|
||||||
- go get -u cloud.google.com/go/civil
|
- go get -u github.com/golang-sql/civil
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- go build
|
- go build
|
||||||
|
|
|
@ -221,23 +221,27 @@ func (r *tdsBuffer) uint16() uint16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tdsBuffer) BVarChar() string {
|
func (r *tdsBuffer) BVarChar() string {
|
||||||
l := int(r.byte())
|
return readBVarCharOrPanic(r)
|
||||||
return r.readUcs2(l)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tdsBuffer) UsVarChar() string {
|
func readBVarCharOrPanic(r io.Reader) string {
|
||||||
l := int(r.uint16())
|
s, err := readBVarChar(r)
|
||||||
return r.readUcs2(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *tdsBuffer) readUcs2(numchars int) string {
|
|
||||||
b := make([]byte, numchars*2)
|
|
||||||
r.ReadFull(b)
|
|
||||||
res, err := ucs22str(b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
badStreamPanic(err)
|
badStreamPanic(err)
|
||||||
}
|
}
|
||||||
return res
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUsVarCharOrPanic(r io.Reader) string {
|
||||||
|
s, err := readUsVarChar(r)
|
||||||
|
if err != nil {
|
||||||
|
badStreamPanic(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *tdsBuffer) UsVarChar() string {
|
||||||
|
return readUsVarCharOrPanic(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tdsBuffer) Read(buf []byte) (copied int, err error) {
|
func (r *tdsBuffer) Read(buf []byte) (copied int, err error) {
|
||||||
|
|
|
@ -7,9 +7,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/denisenkom/go-mssqldb/internal/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bulk struct {
|
type Bulk struct {
|
||||||
|
@ -42,6 +43,11 @@ type BulkOptions struct {
|
||||||
|
|
||||||
type DataValue interface{}
|
type DataValue interface{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
sqlDateFormat = "2006-01-02"
|
||||||
|
sqlTimeFormat = "2006-01-02 15:04:05.999999999Z07:00"
|
||||||
|
)
|
||||||
|
|
||||||
func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) {
|
func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) {
|
||||||
b := Bulk{ctx: context.Background(), cn: cn, tablename: table, headerSent: false, columnsName: columns}
|
b := Bulk{ctx: context.Background(), cn: cn, tablename: table, headerSent: false, columnsName: columns}
|
||||||
b.Debug = false
|
b.Debug = false
|
||||||
|
@ -334,7 +340,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case int64:
|
case int64:
|
||||||
intvalue = val
|
intvalue = val
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for int column")
|
err = fmt.Errorf("mssql: invalid type for int column: %T", val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +367,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case int64:
|
case int64:
|
||||||
floatvalue = float64(val)
|
floatvalue = float64(val)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for float column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for float column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +386,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case []byte:
|
case []byte:
|
||||||
res.buffer = val
|
res.buffer = val
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for nvarchar column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for nvarchar column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.ti.Size = len(res.buffer)
|
res.ti.Size = len(res.buffer)
|
||||||
|
@ -392,14 +398,14 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case []byte:
|
case []byte:
|
||||||
res.buffer = val
|
res.buffer = val
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for varchar column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for varchar column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.ti.Size = len(res.buffer)
|
res.ti.Size = len(res.buffer)
|
||||||
|
|
||||||
case typeBit, typeBitN:
|
case typeBit, typeBitN:
|
||||||
if reflect.TypeOf(val).Kind() != reflect.Bool {
|
if reflect.TypeOf(val).Kind() != reflect.Bool {
|
||||||
err = fmt.Errorf("mssql: invalid type for bit column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for bit column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.ti.TypeId = typeBitN
|
res.ti.TypeId = typeBitN
|
||||||
|
@ -413,18 +419,31 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case time.Time:
|
case time.Time:
|
||||||
res.buffer = encodeDateTime2(val, int(col.ti.Scale))
|
res.buffer = encodeDateTime2(val, int(col.ti.Scale))
|
||||||
res.ti.Size = len(res.buffer)
|
res.ti.Size = len(res.buffer)
|
||||||
|
case string:
|
||||||
|
var t time.Time
|
||||||
|
if t, err = time.Parse(sqlTimeFormat, val); err != nil {
|
||||||
|
return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
|
||||||
|
}
|
||||||
|
res.buffer = encodeDateTime2(t, int(col.ti.Scale))
|
||||||
|
res.ti.Size = len(res.buffer)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for datetime2 column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for datetime2 column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case typeDateTimeOffsetN:
|
case typeDateTimeOffsetN:
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
res.buffer = encodeDateTimeOffset(val, int(res.ti.Scale))
|
res.buffer = encodeDateTimeOffset(val, int(col.ti.Scale))
|
||||||
|
res.ti.Size = len(res.buffer)
|
||||||
|
case string:
|
||||||
|
var t time.Time
|
||||||
|
if t, err = time.Parse(sqlTimeFormat, val); err != nil {
|
||||||
|
return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
|
||||||
|
}
|
||||||
|
res.buffer = encodeDateTimeOffset(t, int(col.ti.Scale))
|
||||||
res.ti.Size = len(res.buffer)
|
res.ti.Size = len(res.buffer)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case typeDateN:
|
case typeDateN:
|
||||||
|
@ -432,69 +451,79 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
case time.Time:
|
case time.Time:
|
||||||
res.buffer = encodeDate(val)
|
res.buffer = encodeDate(val)
|
||||||
res.ti.Size = len(res.buffer)
|
res.ti.Size = len(res.buffer)
|
||||||
|
case string:
|
||||||
|
var t time.Time
|
||||||
|
if t, err = time.ParseInLocation(sqlDateFormat, val, time.UTC); err != nil {
|
||||||
|
return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
|
||||||
|
}
|
||||||
|
res.buffer = encodeDate(t)
|
||||||
|
res.ti.Size = len(res.buffer)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for date column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for date column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case typeDateTime, typeDateTimeN, typeDateTim4:
|
case typeDateTime, typeDateTimeN, typeDateTim4:
|
||||||
|
var t time.Time
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
if col.ti.Size == 4 {
|
t = val
|
||||||
res.buffer = encodeDateTim4(val)
|
case string:
|
||||||
res.ti.Size = len(res.buffer)
|
if t, err = time.Parse(sqlTimeFormat, val); err != nil {
|
||||||
} else if col.ti.Size == 8 {
|
return res, fmt.Errorf("bulk: unable to convert string to date: %v", err)
|
||||||
res.buffer = encodeDateTime(val)
|
|
||||||
res.ti.Size = len(res.buffer)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("mssql: invalid size of column")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for datetime column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for datetime column: %T %s", val, val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if col.ti.Size == 4 {
|
||||||
|
res.buffer = encodeDateTim4(t)
|
||||||
|
res.ti.Size = len(res.buffer)
|
||||||
|
} else if col.ti.Size == 8 {
|
||||||
|
res.buffer = encodeDateTime(t)
|
||||||
|
res.ti.Size = len(res.buffer)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("mssql: invalid size of column %d", col.ti.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case typeMoney, typeMoney4, typeMoneyN:
|
// case typeMoney, typeMoney4, typeMoneyN:
|
||||||
case typeDecimal, typeDecimalN, typeNumeric, typeNumericN:
|
case typeDecimal, typeDecimalN, typeNumeric, typeNumericN:
|
||||||
var value float64
|
prec := col.ti.Prec
|
||||||
|
scale := col.ti.Scale
|
||||||
|
var dec decimal.Decimal
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case int:
|
case int:
|
||||||
value = float64(v)
|
dec = decimal.Int64ToDecimalScale(int64(v), 0)
|
||||||
case int8:
|
case int8:
|
||||||
value = float64(v)
|
dec = decimal.Int64ToDecimalScale(int64(v), 0)
|
||||||
case int16:
|
case int16:
|
||||||
value = float64(v)
|
dec = decimal.Int64ToDecimalScale(int64(v), 0)
|
||||||
case int32:
|
case int32:
|
||||||
value = float64(v)
|
dec = decimal.Int64ToDecimalScale(int64(v), 0)
|
||||||
case int64:
|
case int64:
|
||||||
value = float64(v)
|
dec = decimal.Int64ToDecimalScale(int64(v), 0)
|
||||||
case float32:
|
case float32:
|
||||||
value = float64(v)
|
dec, err = decimal.Float64ToDecimalScale(float64(v), scale)
|
||||||
case float64:
|
case float64:
|
||||||
value = v
|
dec, err = decimal.Float64ToDecimalScale(float64(v), scale)
|
||||||
case string:
|
case string:
|
||||||
if value, err = strconv.ParseFloat(v, 64); err != nil {
|
dec, err = decimal.StringToDecimalScale(v, scale)
|
||||||
return res, fmt.Errorf("bulk: unable to convert string to float: %v", err)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return res, fmt.Errorf("unknown value for decimal: %#v", v)
|
return res, fmt.Errorf("unknown value for decimal: %T %#v", v, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
perc := col.ti.Prec
|
|
||||||
scale := col.ti.Scale
|
|
||||||
var dec Decimal
|
|
||||||
dec, err = Float64ToDecimalScale(value, scale)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
dec.prec = perc
|
dec.SetPrec(prec)
|
||||||
|
|
||||||
var length byte
|
var length byte
|
||||||
switch {
|
switch {
|
||||||
case perc <= 9:
|
case prec <= 9:
|
||||||
length = 4
|
length = 4
|
||||||
case perc <= 19:
|
case prec <= 19:
|
||||||
length = 8
|
length = 8
|
||||||
case perc <= 28:
|
case prec <= 28:
|
||||||
length = 12
|
length = 12
|
||||||
default:
|
default:
|
||||||
length = 16
|
length = 16
|
||||||
|
@ -504,7 +533,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
// first byte length written by typeInfo.writer
|
// first byte length written by typeInfo.writer
|
||||||
res.ti.Size = int(length) + 1
|
res.ti.Size = int(length) + 1
|
||||||
// second byte sign
|
// second byte sign
|
||||||
if value < 0 {
|
if !dec.IsPositive() {
|
||||||
buf[0] = 0
|
buf[0] = 0
|
||||||
} else {
|
} else {
|
||||||
buf[0] = 1
|
buf[0] = 1
|
||||||
|
@ -527,7 +556,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
res.ti.Size = len(val)
|
res.ti.Size = len(val)
|
||||||
res.buffer = val
|
res.buffer = val
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for Binary column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for Binary column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case typeGuid:
|
case typeGuid:
|
||||||
|
@ -536,7 +565,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
|
||||||
res.ti.Size = len(val)
|
res.ti.Size = len(val)
|
||||||
res.buffer = val
|
res.buffer = val
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("mssql: invalid type for Guid column: %s", val)
|
err = fmt.Errorf("mssql: invalid type for Guid column: %T %s", val, val)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,456 @@
|
||||||
|
package mssql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connectParams struct {
|
||||||
|
logFlags uint64
|
||||||
|
port uint64
|
||||||
|
host string
|
||||||
|
instance string
|
||||||
|
database string
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
dial_timeout time.Duration
|
||||||
|
conn_timeout time.Duration
|
||||||
|
keepAlive time.Duration
|
||||||
|
encrypt bool
|
||||||
|
disableEncryption bool
|
||||||
|
trustServerCertificate bool
|
||||||
|
certificate string
|
||||||
|
hostInCertificate string
|
||||||
|
hostInCertificateProvided bool
|
||||||
|
serverSPN string
|
||||||
|
workstation string
|
||||||
|
appname string
|
||||||
|
typeFlags uint8
|
||||||
|
failOverPartner string
|
||||||
|
failOverPort uint64
|
||||||
|
packetSize uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConnectParams(dsn string) (connectParams, error) {
|
||||||
|
var p connectParams
|
||||||
|
|
||||||
|
var params map[string]string
|
||||||
|
if strings.HasPrefix(dsn, "odbc:") {
|
||||||
|
parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):])
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
params = parameters
|
||||||
|
} else if strings.HasPrefix(dsn, "sqlserver://") {
|
||||||
|
parameters, err := splitConnectionStringURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
params = parameters
|
||||||
|
} else {
|
||||||
|
params = splitConnectionString(dsn)
|
||||||
|
}
|
||||||
|
|
||||||
|
strlog, ok := params["log"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
p.logFlags, err = strconv.ParseUint(strlog, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server := params["server"]
|
||||||
|
parts := strings.SplitN(server, `\`, 2)
|
||||||
|
p.host = parts[0]
|
||||||
|
if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" {
|
||||||
|
p.host = "localhost"
|
||||||
|
}
|
||||||
|
if len(parts) > 1 {
|
||||||
|
p.instance = parts[1]
|
||||||
|
}
|
||||||
|
p.database = params["database"]
|
||||||
|
p.user = params["user id"]
|
||||||
|
p.password = params["password"]
|
||||||
|
|
||||||
|
p.port = 1433
|
||||||
|
strport, ok := params["port"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
p.port, err = strconv.ParseUint(strport, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid tcp port '%v': %v"
|
||||||
|
return p, fmt.Errorf(f, strport, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
|
||||||
|
// Default packet size remains at 4096 bytes
|
||||||
|
p.packetSize = 4096
|
||||||
|
strpsize, ok := params["packet size"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
psize, err := strconv.ParseUint(strpsize, 0, 16)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid packet size '%v': %v"
|
||||||
|
return p, fmt.Errorf(f, strpsize, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes
|
||||||
|
// NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request
|
||||||
|
// a higher packet size, the server will respond with an ENVCHANGE request to
|
||||||
|
// alter the packet size to 16383 bytes.
|
||||||
|
p.packetSize = uint16(psize)
|
||||||
|
if p.packetSize < 512 {
|
||||||
|
p.packetSize = 512
|
||||||
|
} else if p.packetSize > 32767 {
|
||||||
|
p.packetSize = 32767
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
||||||
|
//
|
||||||
|
// Do not set a connection timeout. Use Context to manage such things.
|
||||||
|
// Default to zero, but still allow it to be set.
|
||||||
|
if strconntimeout, ok := params["connection timeout"]; ok {
|
||||||
|
timeout, err := strconv.ParseUint(strconntimeout, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid connection timeout '%v': %v"
|
||||||
|
return p, fmt.Errorf(f, strconntimeout, err.Error())
|
||||||
|
}
|
||||||
|
p.conn_timeout = time.Duration(timeout) * time.Second
|
||||||
|
}
|
||||||
|
p.dial_timeout = 15 * time.Second
|
||||||
|
if strdialtimeout, ok := params["dial timeout"]; ok {
|
||||||
|
timeout, err := strconv.ParseUint(strdialtimeout, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid dial timeout '%v': %v"
|
||||||
|
return p, fmt.Errorf(f, strdialtimeout, err.Error())
|
||||||
|
}
|
||||||
|
p.dial_timeout = time.Duration(timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// default keep alive should be 30 seconds according to spec:
|
||||||
|
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
||||||
|
p.keepAlive = 30 * time.Second
|
||||||
|
if keepAlive, ok := params["keepalive"]; ok {
|
||||||
|
timeout, err := strconv.ParseUint(keepAlive, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid keepAlive value '%s': %s"
|
||||||
|
return p, fmt.Errorf(f, keepAlive, err.Error())
|
||||||
|
}
|
||||||
|
p.keepAlive = time.Duration(timeout) * time.Second
|
||||||
|
}
|
||||||
|
encrypt, ok := params["encrypt"]
|
||||||
|
if ok {
|
||||||
|
if strings.EqualFold(encrypt, "DISABLE") {
|
||||||
|
p.disableEncryption = true
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
p.encrypt, err = strconv.ParseBool(encrypt)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid encrypt '%s': %s"
|
||||||
|
return p, fmt.Errorf(f, encrypt, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.trustServerCertificate = true
|
||||||
|
}
|
||||||
|
trust, ok := params["trustservercertificate"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
p.trustServerCertificate, err = strconv.ParseBool(trust)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid trust server certificate '%s': %s"
|
||||||
|
return p, fmt.Errorf(f, trust, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.certificate = params["certificate"]
|
||||||
|
p.hostInCertificate, ok = params["hostnameincertificate"]
|
||||||
|
if ok {
|
||||||
|
p.hostInCertificateProvided = true
|
||||||
|
} else {
|
||||||
|
p.hostInCertificate = p.host
|
||||||
|
p.hostInCertificateProvided = false
|
||||||
|
}
|
||||||
|
|
||||||
|
serverSPN, ok := params["serverspn"]
|
||||||
|
if ok {
|
||||||
|
p.serverSPN = serverSPN
|
||||||
|
} else {
|
||||||
|
p.serverSPN = fmt.Sprintf("MSSQLSvc/%s:%d", p.host, p.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
workstation, ok := params["workstation id"]
|
||||||
|
if ok {
|
||||||
|
p.workstation = workstation
|
||||||
|
} else {
|
||||||
|
workstation, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
p.workstation = workstation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appname, ok := params["app name"]
|
||||||
|
if !ok {
|
||||||
|
appname = "go-mssqldb"
|
||||||
|
}
|
||||||
|
p.appname = appname
|
||||||
|
|
||||||
|
appintent, ok := params["applicationintent"]
|
||||||
|
if ok {
|
||||||
|
if appintent == "ReadOnly" {
|
||||||
|
if p.database == "" {
|
||||||
|
return p, fmt.Errorf("Database must be specified when ApplicationIntent is ReadOnly")
|
||||||
|
}
|
||||||
|
p.typeFlags |= fReadOnlyIntent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
failOverPartner, ok := params["failoverpartner"]
|
||||||
|
if ok {
|
||||||
|
p.failOverPartner = failOverPartner
|
||||||
|
}
|
||||||
|
|
||||||
|
failOverPort, ok := params["failoverport"]
|
||||||
|
if ok {
|
||||||
|
var err error
|
||||||
|
p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16)
|
||||||
|
if err != nil {
|
||||||
|
f := "Invalid tcp port '%v': %v"
|
||||||
|
return p, fmt.Errorf(f, failOverPort, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitConnectionString(dsn string) (res map[string]string) {
|
||||||
|
res = map[string]string{}
|
||||||
|
parts := strings.Split(dsn, ";")
|
||||||
|
for _, part := range parts {
|
||||||
|
if len(part) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lst := strings.SplitN(part, "=", 2)
|
||||||
|
name := strings.TrimSpace(strings.ToLower(lst[0]))
|
||||||
|
if len(name) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var value string = ""
|
||||||
|
if len(lst) > 1 {
|
||||||
|
value = strings.TrimSpace(lst[1])
|
||||||
|
}
|
||||||
|
res[name] = value
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value
|
||||||
|
func splitConnectionStringURL(dsn string) (map[string]string, error) {
|
||||||
|
res := map[string]string{}
|
||||||
|
|
||||||
|
u, err := url.Parse(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "sqlserver" {
|
||||||
|
return res, fmt.Errorf("scheme %s is not recognized", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
res["user id"] = u.User.Username()
|
||||||
|
p, exists := u.User.Password()
|
||||||
|
if exists {
|
||||||
|
res["password"] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
host = u.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
res["server"] = host + "\\" + u.Path[1:]
|
||||||
|
} else {
|
||||||
|
res["server"] = host
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(port) > 0 {
|
||||||
|
res["port"] = port
|
||||||
|
}
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
for k, v := range query {
|
||||||
|
if len(v) > 1 {
|
||||||
|
return res, fmt.Errorf("key %s provided more than once", k)
|
||||||
|
}
|
||||||
|
res[strings.ToLower(k)] = v[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits a URL in the ODBC format
|
||||||
|
func splitConnectionStringOdbc(dsn string) (map[string]string, error) {
|
||||||
|
res := map[string]string{}
|
||||||
|
|
||||||
|
type parserState int
|
||||||
|
const (
|
||||||
|
// Before the start of a key
|
||||||
|
parserStateBeforeKey parserState = iota
|
||||||
|
|
||||||
|
// Inside a key
|
||||||
|
parserStateKey
|
||||||
|
|
||||||
|
// Beginning of a value. May be bare or braced
|
||||||
|
parserStateBeginValue
|
||||||
|
|
||||||
|
// Inside a bare value
|
||||||
|
parserStateBareValue
|
||||||
|
|
||||||
|
// Inside a braced value
|
||||||
|
parserStateBracedValue
|
||||||
|
|
||||||
|
// A closing brace inside a braced value.
|
||||||
|
// May be the end of the value or an escaped closing brace, depending on the next character
|
||||||
|
parserStateBracedValueClosingBrace
|
||||||
|
|
||||||
|
// After a value. Next character should be a semicolon or whitespace.
|
||||||
|
parserStateEndValue
|
||||||
|
)
|
||||||
|
|
||||||
|
var state = parserStateBeforeKey
|
||||||
|
|
||||||
|
var key string
|
||||||
|
var value string
|
||||||
|
|
||||||
|
for i, c := range dsn {
|
||||||
|
switch state {
|
||||||
|
case parserStateBeforeKey:
|
||||||
|
switch {
|
||||||
|
case c == '=':
|
||||||
|
return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i)
|
||||||
|
case !unicode.IsSpace(c) && c != ';':
|
||||||
|
state = parserStateKey
|
||||||
|
key += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateKey:
|
||||||
|
switch c {
|
||||||
|
case '=':
|
||||||
|
key = normalizeOdbcKey(key)
|
||||||
|
state = parserStateBeginValue
|
||||||
|
|
||||||
|
case ';':
|
||||||
|
// Key without value
|
||||||
|
key = normalizeOdbcKey(key)
|
||||||
|
res[key] = value
|
||||||
|
key = ""
|
||||||
|
value = ""
|
||||||
|
state = parserStateBeforeKey
|
||||||
|
|
||||||
|
default:
|
||||||
|
key += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateBeginValue:
|
||||||
|
switch {
|
||||||
|
case c == '{':
|
||||||
|
state = parserStateBracedValue
|
||||||
|
case c == ';':
|
||||||
|
// Empty value
|
||||||
|
res[key] = value
|
||||||
|
key = ""
|
||||||
|
state = parserStateBeforeKey
|
||||||
|
case unicode.IsSpace(c):
|
||||||
|
// Ignore whitespace
|
||||||
|
default:
|
||||||
|
state = parserStateBareValue
|
||||||
|
value += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateBareValue:
|
||||||
|
if c == ';' {
|
||||||
|
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
|
||||||
|
key = ""
|
||||||
|
value = ""
|
||||||
|
state = parserStateBeforeKey
|
||||||
|
} else {
|
||||||
|
value += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateBracedValue:
|
||||||
|
if c == '}' {
|
||||||
|
state = parserStateBracedValueClosingBrace
|
||||||
|
} else {
|
||||||
|
value += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateBracedValueClosingBrace:
|
||||||
|
if c == '}' {
|
||||||
|
// Escaped closing brace
|
||||||
|
value += string(c)
|
||||||
|
state = parserStateBracedValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of braced value
|
||||||
|
res[key] = value
|
||||||
|
key = ""
|
||||||
|
value = ""
|
||||||
|
|
||||||
|
// This character is the first character past the end,
|
||||||
|
// so it needs to be parsed like the parserStateEndValue state.
|
||||||
|
state = parserStateEndValue
|
||||||
|
switch {
|
||||||
|
case c == ';':
|
||||||
|
state = parserStateBeforeKey
|
||||||
|
case unicode.IsSpace(c):
|
||||||
|
// Ignore whitespace
|
||||||
|
default:
|
||||||
|
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
case parserStateEndValue:
|
||||||
|
switch {
|
||||||
|
case c == ';':
|
||||||
|
state = parserStateBeforeKey
|
||||||
|
case unicode.IsSpace(c):
|
||||||
|
// Ignore whitespace
|
||||||
|
default:
|
||||||
|
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case parserStateBeforeKey: // Okay
|
||||||
|
case parserStateKey: // Unfinished key. Treat as key without value.
|
||||||
|
key = normalizeOdbcKey(key)
|
||||||
|
res[key] = value
|
||||||
|
case parserStateBeginValue: // Empty value
|
||||||
|
res[key] = value
|
||||||
|
case parserStateBareValue:
|
||||||
|
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
|
||||||
|
case parserStateBracedValue:
|
||||||
|
return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn))
|
||||||
|
case parserStateBracedValueClosingBrace: // End of braced value
|
||||||
|
res[key] = value
|
||||||
|
case parserStateEndValue: // Okay
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalizes the given string as an ODBC-format key
|
||||||
|
func normalizeOdbcKey(s string) string {
|
||||||
|
return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace))
|
||||||
|
}
|
|
@ -1,131 +0,0 @@
|
||||||
package mssql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// http://msdn.microsoft.com/en-us/library/ee780893.aspx
|
|
||||||
type Decimal struct {
|
|
||||||
integer [4]uint32
|
|
||||||
positive bool
|
|
||||||
prec uint8
|
|
||||||
scale uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaletblflt64 [39]float64
|
|
||||||
|
|
||||||
func (d Decimal) ToFloat64() float64 {
|
|
||||||
val := float64(0)
|
|
||||||
for i := 3; i >= 0; i-- {
|
|
||||||
val *= 0x100000000
|
|
||||||
val += float64(d.integer[i])
|
|
||||||
}
|
|
||||||
if !d.positive {
|
|
||||||
val = -val
|
|
||||||
}
|
|
||||||
if d.scale != 0 {
|
|
||||||
val /= scaletblflt64[d.scale]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoScale = 100
|
|
||||||
|
|
||||||
func Float64ToDecimal(f float64) (Decimal, error) {
|
|
||||||
return Float64ToDecimalScale(f, autoScale)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
|
|
||||||
var dec Decimal
|
|
||||||
if math.IsNaN(f) {
|
|
||||||
return dec, errors.New("NaN")
|
|
||||||
}
|
|
||||||
if math.IsInf(f, 0) {
|
|
||||||
return dec, errors.New("Infinity can't be converted to decimal")
|
|
||||||
}
|
|
||||||
dec.positive = f >= 0
|
|
||||||
if !dec.positive {
|
|
||||||
f = math.Abs(f)
|
|
||||||
}
|
|
||||||
if f > 3.402823669209385e+38 {
|
|
||||||
return dec, errors.New("Float value is out of range")
|
|
||||||
}
|
|
||||||
dec.prec = 20
|
|
||||||
var integer float64
|
|
||||||
for dec.scale = 0; dec.scale <= scale; dec.scale++ {
|
|
||||||
integer = f * scaletblflt64[dec.scale]
|
|
||||||
_, frac := math.Modf(integer)
|
|
||||||
if frac == 0 && scale == autoScale {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
mod := math.Mod(integer, 0x100000000)
|
|
||||||
integer -= mod
|
|
||||||
integer /= 0x100000000
|
|
||||||
dec.integer[i] = uint32(mod)
|
|
||||||
}
|
|
||||||
return dec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var acc float64 = 1
|
|
||||||
for i := 0; i <= 38; i++ {
|
|
||||||
scaletblflt64[i] = acc
|
|
||||||
acc *= 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Decimal) BigInt() big.Int {
|
|
||||||
bytes := make([]byte, 16)
|
|
||||||
binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
|
|
||||||
binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
|
|
||||||
binary.BigEndian.PutUint32(bytes[8:12], d.integer[1])
|
|
||||||
binary.BigEndian.PutUint32(bytes[12:16], d.integer[0])
|
|
||||||
var x big.Int
|
|
||||||
x.SetBytes(bytes)
|
|
||||||
if !d.positive {
|
|
||||||
x.Neg(&x)
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Decimal) Bytes() []byte {
|
|
||||||
x := d.BigInt()
|
|
||||||
return scaleBytes(x.String(), d.scale)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Decimal) UnscaledBytes() []byte {
|
|
||||||
x := d.BigInt()
|
|
||||||
return x.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func scaleBytes(s string, scale uint8) []byte {
|
|
||||||
z := make([]byte, 0, len(s)+1)
|
|
||||||
if s[0] == '-' || s[0] == '+' {
|
|
||||||
z = append(z, byte(s[0]))
|
|
||||||
s = s[1:]
|
|
||||||
}
|
|
||||||
pos := len(s) - int(scale)
|
|
||||||
if pos <= 0 {
|
|
||||||
z = append(z, byte('0'))
|
|
||||||
} else if pos > 0 {
|
|
||||||
z = append(z, []byte(s[:pos])...)
|
|
||||||
}
|
|
||||||
if scale > 0 {
|
|
||||||
z = append(z, byte('.'))
|
|
||||||
for pos < 0 {
|
|
||||||
z = append(z, byte('0'))
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
z = append(z, []byte(s[pos:])...)
|
|
||||||
}
|
|
||||||
return z
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Decimal) String() string {
|
|
||||||
return string(d.Bytes())
|
|
||||||
}
|
|
|
@ -3,8 +3,6 @@ module github.com/denisenkom/go-mssqldb
|
||||||
go 1.11
|
go 1.11
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.37.4
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,168 +1,5 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0=
|
|
||||||
cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA=
|
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
|
||||||
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
|
||||||
go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
|
|
||||||
go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M=
|
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
|
||||||
golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU=
|
|
||||||
google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
|
252
vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go
generated
vendored
Normal file
252
vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go
generated
vendored
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package decimal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decimal represents decimal type in the Microsoft Open Specifications: http://msdn.microsoft.com/en-us/library/ee780893.aspx
|
||||||
|
type Decimal struct {
|
||||||
|
integer [4]uint32 // Little-endian
|
||||||
|
positive bool
|
||||||
|
prec uint8
|
||||||
|
scale uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
scaletblflt64 [39]float64
|
||||||
|
int10 big.Int
|
||||||
|
int1e5 big.Int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var acc float64 = 1
|
||||||
|
for i := 0; i <= 38; i++ {
|
||||||
|
scaletblflt64[i] = acc
|
||||||
|
acc *= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
int10.SetInt64(10)
|
||||||
|
int1e5.SetInt64(1e5)
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoScale = 100
|
||||||
|
|
||||||
|
// SetInteger sets the ind'th element in the integer array
|
||||||
|
func (d *Decimal) SetInteger(integer uint32, ind uint8) {
|
||||||
|
d.integer[ind] = integer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPositive sets the positive member
|
||||||
|
func (d *Decimal) SetPositive(positive bool) {
|
||||||
|
d.positive = positive
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrec sets the prec member
|
||||||
|
func (d *Decimal) SetPrec(prec uint8) {
|
||||||
|
d.prec = prec
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScale sets the scale member
|
||||||
|
func (d *Decimal) SetScale(scale uint8) {
|
||||||
|
d.scale = scale
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPositive returns true if the Decimal is positive
|
||||||
|
func (d *Decimal) IsPositive() bool {
|
||||||
|
return d.positive
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 converts decimal to a float64
|
||||||
|
func (d Decimal) ToFloat64() float64 {
|
||||||
|
val := float64(0)
|
||||||
|
for i := 3; i >= 0; i-- {
|
||||||
|
val *= 0x100000000
|
||||||
|
val += float64(d.integer[i])
|
||||||
|
}
|
||||||
|
if !d.positive {
|
||||||
|
val = -val
|
||||||
|
}
|
||||||
|
if d.scale != 0 {
|
||||||
|
val /= scaletblflt64[d.scale]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigInt converts decimal to a bigint
|
||||||
|
func (d Decimal) BigInt() big.Int {
|
||||||
|
bytes := make([]byte, 16)
|
||||||
|
binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
|
||||||
|
binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
|
||||||
|
binary.BigEndian.PutUint32(bytes[8:12], d.integer[1])
|
||||||
|
binary.BigEndian.PutUint32(bytes[12:16], d.integer[0])
|
||||||
|
var x big.Int
|
||||||
|
x.SetBytes(bytes)
|
||||||
|
if !d.positive {
|
||||||
|
x.Neg(&x)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes converts decimal to a scaled byte slice
|
||||||
|
func (d Decimal) Bytes() []byte {
|
||||||
|
x := d.BigInt()
|
||||||
|
return ScaleBytes(x.String(), d.scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnscaledBytes converts decimal to a unscaled byte slice
|
||||||
|
func (d Decimal) UnscaledBytes() []byte {
|
||||||
|
x := d.BigInt()
|
||||||
|
return x.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String converts decimal to a string
|
||||||
|
func (d Decimal) String() string {
|
||||||
|
return string(d.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64ToDecimal converts float64 to decimal
|
||||||
|
func Float64ToDecimal(f float64) (Decimal, error) {
|
||||||
|
return Float64ToDecimalScale(f, autoScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64ToDecimalScale converts float64 to decimal; user can specify the scale
|
||||||
|
func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
|
||||||
|
var dec Decimal
|
||||||
|
if math.IsNaN(f) {
|
||||||
|
return dec, errors.New("NaN")
|
||||||
|
}
|
||||||
|
if math.IsInf(f, 0) {
|
||||||
|
return dec, errors.New("Infinity can't be converted to decimal")
|
||||||
|
}
|
||||||
|
dec.positive = f >= 0
|
||||||
|
if !dec.positive {
|
||||||
|
f = math.Abs(f)
|
||||||
|
}
|
||||||
|
if f > 3.402823669209385e+38 {
|
||||||
|
return dec, errors.New("Float value is out of range")
|
||||||
|
}
|
||||||
|
dec.prec = 20
|
||||||
|
var integer float64
|
||||||
|
for dec.scale = 0; dec.scale <= scale; dec.scale++ {
|
||||||
|
integer = f * scaletblflt64[dec.scale]
|
||||||
|
_, frac := math.Modf(integer)
|
||||||
|
if frac == 0 && scale == autoScale {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
mod := math.Mod(integer, 0x100000000)
|
||||||
|
integer -= mod
|
||||||
|
integer /= 0x100000000
|
||||||
|
dec.integer[i] = uint32(mod)
|
||||||
|
if mod-math.Trunc(mod) >= 0.5 {
|
||||||
|
dec.integer[i] = uint32(mod) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ToDecimalScale converts float64 to decimal; user can specify the scale
|
||||||
|
func Int64ToDecimalScale(v int64, scale uint8) Decimal {
|
||||||
|
positive := v >= 0
|
||||||
|
if !positive {
|
||||||
|
if v == math.MinInt64 {
|
||||||
|
// Special case - can't negate
|
||||||
|
return Decimal{
|
||||||
|
integer: [4]uint32{0, 0x80000000, 0, 0},
|
||||||
|
positive: false,
|
||||||
|
prec: 20,
|
||||||
|
scale: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
return Decimal{
|
||||||
|
integer: [4]uint32{uint32(v), uint32(v >> 32), 0, 0},
|
||||||
|
positive: positive,
|
||||||
|
prec: 20,
|
||||||
|
scale: scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToDecimalScale converts string to decimal
|
||||||
|
func StringToDecimalScale(v string, outScale uint8) (Decimal, error) {
|
||||||
|
var r big.Int
|
||||||
|
var unscaled string
|
||||||
|
var inScale int
|
||||||
|
|
||||||
|
point := strings.LastIndexByte(v, '.')
|
||||||
|
if point == -1 {
|
||||||
|
inScale = 0
|
||||||
|
unscaled = v
|
||||||
|
} else {
|
||||||
|
inScale = len(v) - point - 1
|
||||||
|
unscaled = v[:point] + v[point+1:]
|
||||||
|
}
|
||||||
|
if inScale > math.MaxUint8 {
|
||||||
|
return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale too large", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := r.SetString(unscaled, 10)
|
||||||
|
if !ok {
|
||||||
|
return Decimal{}, fmt.Errorf("can't parse %q as a decimal number", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inScale > int(outScale) {
|
||||||
|
return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale %d is larger than the scale %d of the target column", v, inScale, outScale)
|
||||||
|
}
|
||||||
|
for inScale < int(outScale) {
|
||||||
|
if int(outScale)-inScale >= 5 {
|
||||||
|
r.Mul(&r, &int1e5)
|
||||||
|
inScale += 5
|
||||||
|
} else {
|
||||||
|
r.Mul(&r, &int10)
|
||||||
|
inScale++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes := r.Bytes()
|
||||||
|
if len(bytes) > 16 {
|
||||||
|
return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: precision too large", v)
|
||||||
|
}
|
||||||
|
var out [4]uint32
|
||||||
|
for i, b := range bytes {
|
||||||
|
pos := len(bytes) - i - 1
|
||||||
|
out[pos/4] += uint32(b) << uint(pos%4*8)
|
||||||
|
}
|
||||||
|
return Decimal{
|
||||||
|
integer: out,
|
||||||
|
positive: r.Sign() >= 0,
|
||||||
|
prec: 20,
|
||||||
|
scale: uint8(inScale),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleBytes converts a stringified decimal to a scaled byte slice
|
||||||
|
func ScaleBytes(s string, scale uint8) []byte {
|
||||||
|
z := make([]byte, 0, len(s)+1)
|
||||||
|
if s[0] == '-' || s[0] == '+' {
|
||||||
|
z = append(z, byte(s[0]))
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
pos := len(s) - int(scale)
|
||||||
|
if pos <= 0 {
|
||||||
|
z = append(z, byte('0'))
|
||||||
|
} else if pos > 0 {
|
||||||
|
z = append(z, []byte(s[:pos])...)
|
||||||
|
}
|
||||||
|
if scale > 0 {
|
||||||
|
z = append(z, byte('.'))
|
||||||
|
for pos < 0 {
|
||||||
|
z = append(z, byte('0'))
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
z = append(z, []byte(s[pos:])...)
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
|
@ -716,6 +716,8 @@ func (rc *Rows) Next(dest []driver.Value) error {
|
||||||
if tokdata.isError() {
|
if tokdata.isError() {
|
||||||
return rc.stmt.c.checkBadConn(tokdata.getError())
|
return rc.stmt.c.checkBadConn(tokdata.getError())
|
||||||
}
|
}
|
||||||
|
case ReturnStatus:
|
||||||
|
rc.stmt.c.setReturnStatus(tokdata)
|
||||||
case error:
|
case error:
|
||||||
return rc.stmt.c.checkBadConn(tokdata)
|
return rc.stmt.c.checkBadConn(tokdata)
|
||||||
}
|
}
|
||||||
|
@ -874,29 +876,6 @@ func (r *Result) RowsAffected() (int64, error) {
|
||||||
return r.rowsAffected, nil
|
return r.rowsAffected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) LastInsertId() (int64, error) {
|
|
||||||
s, err := r.c.Prepare("select cast(@@identity as bigint)")
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
rows, err := s.Query(nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
dest := make([]driver.Value, 1)
|
|
||||||
err = rows.Next(dest)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if dest[0] == nil {
|
|
||||||
return -1, errors.New("There is no generated identity value")
|
|
||||||
}
|
|
||||||
lastInsertId := dest[0].(int64)
|
|
||||||
return lastInsertId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Pinger = &Conn{}
|
var _ driver.Pinger = &Conn{}
|
||||||
|
|
||||||
// Ping is used to check if the remote server is available and satisfies the Pinger interface.
|
// Ping is used to check if the remote server is available and satisfies the Pinger interface.
|
||||||
|
|
|
@ -5,6 +5,7 @@ package mssql
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ driver.Connector = &Connector{}
|
var _ driver.Connector = &Connector{}
|
||||||
|
@ -45,3 +46,7 @@ func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
func (c *Connector) Driver() driver.Driver {
|
func (c *Connector) Driver() driver.Driver {
|
||||||
return c.driver
|
return c.driver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Result) LastInsertId() (int64, error) {
|
||||||
|
return -1, errors.New("LastInsertId is not supported. Please use the OUTPUT clause or add `select ID = convert(bigint, SCOPE_IDENTITY())` to the end of your query.")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// +build !go1.10
|
||||||
|
|
||||||
|
package mssql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Result) LastInsertId() (int64, error) {
|
||||||
|
s, err := r.c.Prepare("select cast(@@identity as bigint)")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
rows, err := s.Query(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
dest := make([]driver.Value, 1)
|
||||||
|
err = rows.Next(dest)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if dest[0] == nil {
|
||||||
|
return -1, errors.New("There is no generated identity value")
|
||||||
|
}
|
||||||
|
lastInsertId := dest[0].(int64)
|
||||||
|
return lastInsertId, nil
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// "github.com/cockroachdb/apd"
|
// "github.com/cockroachdb/apd"
|
||||||
"cloud.google.com/go/civil"
|
"github.com/golang-sql/civil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type alias provided for compatibility.
|
// Type alias provided for compatibility.
|
||||||
|
|
|
@ -9,9 +9,6 @@ import (
|
||||||
type timeoutConn struct {
|
type timeoutConn struct {
|
||||||
c net.Conn
|
c net.Conn
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
buf *tdsBuffer
|
|
||||||
packetPending bool
|
|
||||||
continueRead bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn {
|
func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn {
|
||||||
|
@ -22,32 +19,6 @@ func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *timeoutConn) Read(b []byte) (n int, err error) {
|
func (c *timeoutConn) Read(b []byte) (n int, err error) {
|
||||||
if c.buf != nil {
|
|
||||||
if c.packetPending {
|
|
||||||
c.packetPending = false
|
|
||||||
err = c.buf.FinishPacket()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Cannot send handshake packet: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.continueRead = false
|
|
||||||
}
|
|
||||||
if !c.continueRead {
|
|
||||||
var packet packetType
|
|
||||||
packet, err = c.buf.BeginRead()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Cannot read handshake packet: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if packet != packPrelogin {
|
|
||||||
err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.continueRead = true
|
|
||||||
}
|
|
||||||
n, err = c.buf.Read(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.timeout > 0 {
|
if c.timeout > 0 {
|
||||||
err = c.c.SetDeadline(time.Now().Add(c.timeout))
|
err = c.c.SetDeadline(time.Now().Add(c.timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,17 +29,6 @@ func (c *timeoutConn) Read(b []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *timeoutConn) Write(b []byte) (n int, err error) {
|
func (c *timeoutConn) Write(b []byte) (n int, err error) {
|
||||||
if c.buf != nil {
|
|
||||||
if !c.packetPending {
|
|
||||||
c.buf.BeginPacket(packPrelogin, false)
|
|
||||||
c.packetPending = true
|
|
||||||
}
|
|
||||||
n, err = c.buf.Write(b)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.timeout > 0 {
|
if c.timeout > 0 {
|
||||||
err = c.c.SetDeadline(time.Now().Add(c.timeout))
|
err = c.c.SetDeadline(time.Now().Add(c.timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -101,3 +61,108 @@ func (c timeoutConn) SetReadDeadline(t time.Time) error {
|
||||||
func (c timeoutConn) SetWriteDeadline(t time.Time) error {
|
func (c timeoutConn) SetWriteDeadline(t time.Time) error {
|
||||||
panic("Not implemented")
|
panic("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this connection is used during TLS Handshake
|
||||||
|
// TDS protocol requires TLS handshake messages to be sent inside TDS packets
|
||||||
|
type tlsHandshakeConn struct {
|
||||||
|
buf *tdsBuffer
|
||||||
|
packetPending bool
|
||||||
|
continueRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) Read(b []byte) (n int, err error) {
|
||||||
|
if c.packetPending {
|
||||||
|
c.packetPending = false
|
||||||
|
err = c.buf.FinishPacket()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Cannot send handshake packet: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.continueRead = false
|
||||||
|
}
|
||||||
|
if !c.continueRead {
|
||||||
|
var packet packetType
|
||||||
|
packet, err = c.buf.BeginRead()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Cannot read handshake packet: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if packet != packPrelogin {
|
||||||
|
err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.continueRead = true
|
||||||
|
}
|
||||||
|
return c.buf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) Write(b []byte) (n int, err error) {
|
||||||
|
if !c.packetPending {
|
||||||
|
c.buf.BeginPacket(packPrelogin, false)
|
||||||
|
c.packetPending = true
|
||||||
|
}
|
||||||
|
return c.buf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) Close() error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) LocalAddr() net.Addr {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) RemoteAddr() net.Addr {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) SetDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) SetReadDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsHandshakeConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this connection just delegates all methods to it's wrapped connection
|
||||||
|
// it also allows switching underlying connection on the fly
|
||||||
|
// it is needed because tls.Conn does not allow switching underlying connection
|
||||||
|
type passthroughConn struct {
|
||||||
|
c net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) Read(b []byte) (n int, err error) {
|
||||||
|
return c.c.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) Write(b []byte) (n int, err error) {
|
||||||
|
return c.c.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) Close() error {
|
||||||
|
return c.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) LocalAddr() net.Addr {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) RemoteAddr() net.Addr {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) SetDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) SetReadDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c passthroughConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
|
@ -10,13 +10,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
@ -51,15 +47,13 @@ func parseInstances(msg []byte) map[string]map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) {
|
func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) {
|
||||||
maxTime := 5 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, maxTime)
|
|
||||||
defer cancel()
|
|
||||||
conn, err := d.DialContext(ctx, "udp", address+":1434")
|
conn, err := d.DialContext(ctx, "udp", address+":1434")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
conn.SetDeadline(time.Now().Add(maxTime))
|
deadline, _ := ctx.Deadline()
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
_, err = conn.Write([]byte{3})
|
_, err = conn.Write([]byte{3})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -474,10 +468,9 @@ func readUcs2(r io.Reader, numchars int) (res string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func readUsVarChar(r io.Reader) (res string, err error) {
|
func readUsVarChar(r io.Reader) (res string, err error) {
|
||||||
var numchars uint16
|
numchars, err := readUshort(r)
|
||||||
err = binary.Read(r, binary.LittleEndian, &numchars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return
|
||||||
}
|
}
|
||||||
return readUcs2(r, int(numchars))
|
return readUcs2(r, int(numchars))
|
||||||
}
|
}
|
||||||
|
@ -497,8 +490,7 @@ func writeUsVarChar(w io.Writer, s string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func readBVarChar(r io.Reader) (res string, err error) {
|
func readBVarChar(r io.Reader) (res string, err error) {
|
||||||
var numchars uint8
|
numchars, err := readByte(r)
|
||||||
err = binary.Read(r, binary.LittleEndian, &numchars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -525,8 +517,7 @@ func writeBVarChar(w io.Writer, s string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func readBVarByte(r io.Reader) (res []byte, err error) {
|
func readBVarByte(r io.Reader) (res []byte, err error) {
|
||||||
var length uint8
|
length, err := readByte(r)
|
||||||
err = binary.Read(r, binary.LittleEndian, &length)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -654,458 +645,6 @@ func sendAttention(buf *tdsBuffer) error {
|
||||||
return buf.FinishPacket()
|
return buf.FinishPacket()
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectParams struct {
|
|
||||||
logFlags uint64
|
|
||||||
port uint64
|
|
||||||
host string
|
|
||||||
instance string
|
|
||||||
database string
|
|
||||||
user string
|
|
||||||
password string
|
|
||||||
dial_timeout time.Duration
|
|
||||||
conn_timeout time.Duration
|
|
||||||
keepAlive time.Duration
|
|
||||||
encrypt bool
|
|
||||||
disableEncryption bool
|
|
||||||
trustServerCertificate bool
|
|
||||||
certificate string
|
|
||||||
hostInCertificate string
|
|
||||||
hostInCertificateProvided bool
|
|
||||||
serverSPN string
|
|
||||||
workstation string
|
|
||||||
appname string
|
|
||||||
typeFlags uint8
|
|
||||||
failOverPartner string
|
|
||||||
failOverPort uint64
|
|
||||||
packetSize uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitConnectionString(dsn string) (res map[string]string) {
|
|
||||||
res = map[string]string{}
|
|
||||||
parts := strings.Split(dsn, ";")
|
|
||||||
for _, part := range parts {
|
|
||||||
if len(part) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lst := strings.SplitN(part, "=", 2)
|
|
||||||
name := strings.TrimSpace(strings.ToLower(lst[0]))
|
|
||||||
if len(name) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var value string = ""
|
|
||||||
if len(lst) > 1 {
|
|
||||||
value = strings.TrimSpace(lst[1])
|
|
||||||
}
|
|
||||||
res[name] = value
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Splits a URL in the ODBC format
|
|
||||||
func splitConnectionStringOdbc(dsn string) (map[string]string, error) {
|
|
||||||
res := map[string]string{}
|
|
||||||
|
|
||||||
type parserState int
|
|
||||||
const (
|
|
||||||
// Before the start of a key
|
|
||||||
parserStateBeforeKey parserState = iota
|
|
||||||
|
|
||||||
// Inside a key
|
|
||||||
parserStateKey
|
|
||||||
|
|
||||||
// Beginning of a value. May be bare or braced
|
|
||||||
parserStateBeginValue
|
|
||||||
|
|
||||||
// Inside a bare value
|
|
||||||
parserStateBareValue
|
|
||||||
|
|
||||||
// Inside a braced value
|
|
||||||
parserStateBracedValue
|
|
||||||
|
|
||||||
// A closing brace inside a braced value.
|
|
||||||
// May be the end of the value or an escaped closing brace, depending on the next character
|
|
||||||
parserStateBracedValueClosingBrace
|
|
||||||
|
|
||||||
// After a value. Next character should be a semicolon or whitespace.
|
|
||||||
parserStateEndValue
|
|
||||||
)
|
|
||||||
|
|
||||||
var state = parserStateBeforeKey
|
|
||||||
|
|
||||||
var key string
|
|
||||||
var value string
|
|
||||||
|
|
||||||
for i, c := range dsn {
|
|
||||||
switch state {
|
|
||||||
case parserStateBeforeKey:
|
|
||||||
switch {
|
|
||||||
case c == '=':
|
|
||||||
return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i)
|
|
||||||
case !unicode.IsSpace(c) && c != ';':
|
|
||||||
state = parserStateKey
|
|
||||||
key += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateKey:
|
|
||||||
switch c {
|
|
||||||
case '=':
|
|
||||||
key = normalizeOdbcKey(key)
|
|
||||||
if len(key) == 0 {
|
|
||||||
return res, fmt.Errorf("Unexpected end of key at index %d.", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
state = parserStateBeginValue
|
|
||||||
|
|
||||||
case ';':
|
|
||||||
// Key without value
|
|
||||||
key = normalizeOdbcKey(key)
|
|
||||||
if len(key) == 0 {
|
|
||||||
return res, fmt.Errorf("Unexpected end of key at index %d.", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
res[key] = value
|
|
||||||
key = ""
|
|
||||||
value = ""
|
|
||||||
state = parserStateBeforeKey
|
|
||||||
|
|
||||||
default:
|
|
||||||
key += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateBeginValue:
|
|
||||||
switch {
|
|
||||||
case c == '{':
|
|
||||||
state = parserStateBracedValue
|
|
||||||
case c == ';':
|
|
||||||
// Empty value
|
|
||||||
res[key] = value
|
|
||||||
key = ""
|
|
||||||
state = parserStateBeforeKey
|
|
||||||
case unicode.IsSpace(c):
|
|
||||||
// Ignore whitespace
|
|
||||||
default:
|
|
||||||
state = parserStateBareValue
|
|
||||||
value += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateBareValue:
|
|
||||||
if c == ';' {
|
|
||||||
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
|
|
||||||
key = ""
|
|
||||||
value = ""
|
|
||||||
state = parserStateBeforeKey
|
|
||||||
} else {
|
|
||||||
value += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateBracedValue:
|
|
||||||
if c == '}' {
|
|
||||||
state = parserStateBracedValueClosingBrace
|
|
||||||
} else {
|
|
||||||
value += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateBracedValueClosingBrace:
|
|
||||||
if c == '}' {
|
|
||||||
// Escaped closing brace
|
|
||||||
value += string(c)
|
|
||||||
state = parserStateBracedValue
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of braced value
|
|
||||||
res[key] = value
|
|
||||||
key = ""
|
|
||||||
value = ""
|
|
||||||
|
|
||||||
// This character is the first character past the end,
|
|
||||||
// so it needs to be parsed like the parserStateEndValue state.
|
|
||||||
state = parserStateEndValue
|
|
||||||
switch {
|
|
||||||
case c == ';':
|
|
||||||
state = parserStateBeforeKey
|
|
||||||
case unicode.IsSpace(c):
|
|
||||||
// Ignore whitespace
|
|
||||||
default:
|
|
||||||
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
case parserStateEndValue:
|
|
||||||
switch {
|
|
||||||
case c == ';':
|
|
||||||
state = parserStateBeforeKey
|
|
||||||
case unicode.IsSpace(c):
|
|
||||||
// Ignore whitespace
|
|
||||||
default:
|
|
||||||
return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch state {
|
|
||||||
case parserStateBeforeKey: // Okay
|
|
||||||
case parserStateKey: // Unfinished key. Treat as key without value.
|
|
||||||
key = normalizeOdbcKey(key)
|
|
||||||
if len(key) == 0 {
|
|
||||||
return res, fmt.Errorf("Unexpected end of key at index %d.", len(dsn))
|
|
||||||
}
|
|
||||||
res[key] = value
|
|
||||||
case parserStateBeginValue: // Empty value
|
|
||||||
res[key] = value
|
|
||||||
case parserStateBareValue:
|
|
||||||
res[key] = strings.TrimRightFunc(value, unicode.IsSpace)
|
|
||||||
case parserStateBracedValue:
|
|
||||||
return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn))
|
|
||||||
case parserStateBracedValueClosingBrace: // End of braced value
|
|
||||||
res[key] = value
|
|
||||||
case parserStateEndValue: // Okay
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalizes the given string as an ODBC-format key
|
|
||||||
func normalizeOdbcKey(s string) string {
|
|
||||||
return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value
|
|
||||||
func splitConnectionStringURL(dsn string) (map[string]string, error) {
|
|
||||||
res := map[string]string{}
|
|
||||||
|
|
||||||
u, err := url.Parse(dsn)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != "sqlserver" {
|
|
||||||
return res, fmt.Errorf("scheme %s is not recognized", u.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.User != nil {
|
|
||||||
res["user id"] = u.User.Username()
|
|
||||||
p, exists := u.User.Password()
|
|
||||||
if exists {
|
|
||||||
res["password"] = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
host = u.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
res["server"] = host + "\\" + u.Path[1:]
|
|
||||||
} else {
|
|
||||||
res["server"] = host
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(port) > 0 {
|
|
||||||
res["port"] = port
|
|
||||||
}
|
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
for k, v := range query {
|
|
||||||
if len(v) > 1 {
|
|
||||||
return res, fmt.Errorf("key %s provided more than once", k)
|
|
||||||
}
|
|
||||||
res[strings.ToLower(k)] = v[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseConnectParams(dsn string) (connectParams, error) {
|
|
||||||
var p connectParams
|
|
||||||
|
|
||||||
var params map[string]string
|
|
||||||
if strings.HasPrefix(dsn, "odbc:") {
|
|
||||||
parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):])
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
params = parameters
|
|
||||||
} else if strings.HasPrefix(dsn, "sqlserver://") {
|
|
||||||
parameters, err := splitConnectionStringURL(dsn)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
params = parameters
|
|
||||||
} else {
|
|
||||||
params = splitConnectionString(dsn)
|
|
||||||
}
|
|
||||||
|
|
||||||
strlog, ok := params["log"]
|
|
||||||
if ok {
|
|
||||||
var err error
|
|
||||||
p.logFlags, err = strconv.ParseUint(strlog, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server := params["server"]
|
|
||||||
parts := strings.SplitN(server, `\`, 2)
|
|
||||||
p.host = parts[0]
|
|
||||||
if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" {
|
|
||||||
p.host = "localhost"
|
|
||||||
}
|
|
||||||
if len(parts) > 1 {
|
|
||||||
p.instance = parts[1]
|
|
||||||
}
|
|
||||||
p.database = params["database"]
|
|
||||||
p.user = params["user id"]
|
|
||||||
p.password = params["password"]
|
|
||||||
|
|
||||||
p.port = 1433
|
|
||||||
strport, ok := params["port"]
|
|
||||||
if ok {
|
|
||||||
var err error
|
|
||||||
p.port, err = strconv.ParseUint(strport, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid tcp port '%v': %v"
|
|
||||||
return p, fmt.Errorf(f, strport, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
|
|
||||||
// Default packet size remains at 4096 bytes
|
|
||||||
p.packetSize = 4096
|
|
||||||
strpsize, ok := params["packet size"]
|
|
||||||
if ok {
|
|
||||||
var err error
|
|
||||||
psize, err := strconv.ParseUint(strpsize, 0, 16)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid packet size '%v': %v"
|
|
||||||
return p, fmt.Errorf(f, strpsize, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes
|
|
||||||
// NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request
|
|
||||||
// a higher packet size, the server will respond with an ENVCHANGE request to
|
|
||||||
// alter the packet size to 16383 bytes.
|
|
||||||
p.packetSize = uint16(psize)
|
|
||||||
if p.packetSize < 512 {
|
|
||||||
p.packetSize = 512
|
|
||||||
} else if p.packetSize > 32767 {
|
|
||||||
p.packetSize = 32767
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
|
||||||
//
|
|
||||||
// Do not set a connection timeout. Use Context to manage such things.
|
|
||||||
// Default to zero, but still allow it to be set.
|
|
||||||
if strconntimeout, ok := params["connection timeout"]; ok {
|
|
||||||
timeout, err := strconv.ParseUint(strconntimeout, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid connection timeout '%v': %v"
|
|
||||||
return p, fmt.Errorf(f, strconntimeout, err.Error())
|
|
||||||
}
|
|
||||||
p.conn_timeout = time.Duration(timeout) * time.Second
|
|
||||||
}
|
|
||||||
p.dial_timeout = 15 * time.Second
|
|
||||||
if strdialtimeout, ok := params["dial timeout"]; ok {
|
|
||||||
timeout, err := strconv.ParseUint(strdialtimeout, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid dial timeout '%v': %v"
|
|
||||||
return p, fmt.Errorf(f, strdialtimeout, err.Error())
|
|
||||||
}
|
|
||||||
p.dial_timeout = time.Duration(timeout) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
// default keep alive should be 30 seconds according to spec:
|
|
||||||
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
|
||||||
p.keepAlive = 30 * time.Second
|
|
||||||
if keepAlive, ok := params["keepalive"]; ok {
|
|
||||||
timeout, err := strconv.ParseUint(keepAlive, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid keepAlive value '%s': %s"
|
|
||||||
return p, fmt.Errorf(f, keepAlive, err.Error())
|
|
||||||
}
|
|
||||||
p.keepAlive = time.Duration(timeout) * time.Second
|
|
||||||
}
|
|
||||||
encrypt, ok := params["encrypt"]
|
|
||||||
if ok {
|
|
||||||
if strings.EqualFold(encrypt, "DISABLE") {
|
|
||||||
p.disableEncryption = true
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
p.encrypt, err = strconv.ParseBool(encrypt)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid encrypt '%s': %s"
|
|
||||||
return p, fmt.Errorf(f, encrypt, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.trustServerCertificate = true
|
|
||||||
}
|
|
||||||
trust, ok := params["trustservercertificate"]
|
|
||||||
if ok {
|
|
||||||
var err error
|
|
||||||
p.trustServerCertificate, err = strconv.ParseBool(trust)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid trust server certificate '%s': %s"
|
|
||||||
return p, fmt.Errorf(f, trust, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.certificate = params["certificate"]
|
|
||||||
p.hostInCertificate, ok = params["hostnameincertificate"]
|
|
||||||
if ok {
|
|
||||||
p.hostInCertificateProvided = true
|
|
||||||
} else {
|
|
||||||
p.hostInCertificate = p.host
|
|
||||||
p.hostInCertificateProvided = false
|
|
||||||
}
|
|
||||||
|
|
||||||
serverSPN, ok := params["serverspn"]
|
|
||||||
if ok {
|
|
||||||
p.serverSPN = serverSPN
|
|
||||||
} else {
|
|
||||||
p.serverSPN = fmt.Sprintf("MSSQLSvc/%s:%d", p.host, p.port)
|
|
||||||
}
|
|
||||||
|
|
||||||
workstation, ok := params["workstation id"]
|
|
||||||
if ok {
|
|
||||||
p.workstation = workstation
|
|
||||||
} else {
|
|
||||||
workstation, err := os.Hostname()
|
|
||||||
if err == nil {
|
|
||||||
p.workstation = workstation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appname, ok := params["app name"]
|
|
||||||
if !ok {
|
|
||||||
appname = "go-mssqldb"
|
|
||||||
}
|
|
||||||
p.appname = appname
|
|
||||||
|
|
||||||
appintent, ok := params["applicationintent"]
|
|
||||||
if ok {
|
|
||||||
if appintent == "ReadOnly" {
|
|
||||||
p.typeFlags |= fReadOnlyIntent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
failOverPartner, ok := params["failoverpartner"]
|
|
||||||
if ok {
|
|
||||||
p.failOverPartner = failOverPartner
|
|
||||||
}
|
|
||||||
|
|
||||||
failOverPort, ok := params["failoverport"]
|
|
||||||
if ok {
|
|
||||||
var err error
|
|
||||||
p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16)
|
|
||||||
if err != nil {
|
|
||||||
f := "Invalid tcp port '%v': %v"
|
|
||||||
return p, fmt.Errorf(f, failOverPort, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type auth interface {
|
type auth interface {
|
||||||
InitialBytes() ([]byte, error)
|
InitialBytes() ([]byte, error)
|
||||||
NextBytes([]byte) ([]byte, error)
|
NextBytes([]byte) ([]byte, error)
|
||||||
|
@ -1277,12 +816,12 @@ initiate_connection:
|
||||||
// while SQL Server seems to expect one TCP segment per encrypted TDS package.
|
// while SQL Server seems to expect one TCP segment per encrypted TDS package.
|
||||||
// Setting DynamicRecordSizingDisabled to true disables that algorithm and uses 16384 bytes per TLS package
|
// Setting DynamicRecordSizingDisabled to true disables that algorithm and uses 16384 bytes per TLS package
|
||||||
config.DynamicRecordSizingDisabled = true
|
config.DynamicRecordSizingDisabled = true
|
||||||
outbuf.transport = conn
|
// setting up connection handler which will allow wrapping of TLS handshake packets inside TDS stream
|
||||||
toconn.buf = outbuf
|
handshakeConn := tlsHandshakeConn{buf: outbuf}
|
||||||
tlsConn := tls.Client(toconn, &config)
|
passthrough := passthroughConn{c: &handshakeConn}
|
||||||
|
tlsConn := tls.Client(&passthrough, &config)
|
||||||
err = tlsConn.Handshake()
|
err = tlsConn.Handshake()
|
||||||
|
passthrough.c = toconn
|
||||||
toconn.buf = nil
|
|
||||||
outbuf.transport = tlsConn
|
outbuf.transport = tlsConn
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("TLS Handshake failed: %v", err)
|
return nil, fmt.Errorf("TLS Handshake failed: %v", err)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/denisenkom/go-mssqldb/internal/cp"
|
"github.com/denisenkom/go-mssqldb/internal/cp"
|
||||||
|
"github.com/denisenkom/go-mssqldb/internal/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fixed-length data types
|
// fixed-length data types
|
||||||
|
@ -818,12 +819,12 @@ func decodeMoney(buf []byte) []byte {
|
||||||
uint64(buf[1])<<40 |
|
uint64(buf[1])<<40 |
|
||||||
uint64(buf[2])<<48 |
|
uint64(buf[2])<<48 |
|
||||||
uint64(buf[3])<<56)
|
uint64(buf[3])<<56)
|
||||||
return scaleBytes(strconv.FormatInt(money, 10), 4)
|
return decimal.ScaleBytes(strconv.FormatInt(money, 10), 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMoney4(buf []byte) []byte {
|
func decodeMoney4(buf []byte) []byte {
|
||||||
money := int32(binary.LittleEndian.Uint32(buf[0:4]))
|
money := int32(binary.LittleEndian.Uint32(buf[0:4]))
|
||||||
return scaleBytes(strconv.FormatInt(int64(money), 10), 4)
|
return decimal.ScaleBytes(strconv.FormatInt(int64(money), 10), 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeGuid(buf []byte) []byte {
|
func decodeGuid(buf []byte) []byte {
|
||||||
|
@ -835,15 +836,14 @@ func decodeGuid(buf []byte) []byte {
|
||||||
func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte {
|
func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte {
|
||||||
var sign uint8
|
var sign uint8
|
||||||
sign = buf[0]
|
sign = buf[0]
|
||||||
dec := Decimal{
|
var dec decimal.Decimal
|
||||||
positive: sign != 0,
|
dec.SetPositive(sign != 0)
|
||||||
prec: prec,
|
dec.SetPrec(prec)
|
||||||
scale: scale,
|
dec.SetScale(scale)
|
||||||
}
|
|
||||||
buf = buf[1:]
|
buf = buf[1:]
|
||||||
l := len(buf) / 4
|
l := len(buf) / 4
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
dec.integer[i] = binary.LittleEndian.Uint32(buf[0:4])
|
dec.SetInteger(binary.LittleEndian.Uint32(buf[0:4]), uint8(i))
|
||||||
buf = buf[4:]
|
buf = buf[4:]
|
||||||
}
|
}
|
||||||
return dec.Bytes()
|
return dec.Bytes()
|
||||||
|
@ -1186,7 +1186,7 @@ func makeDecl(ti typeInfo) string {
|
||||||
case typeBigChar, typeChar:
|
case typeBigChar, typeChar:
|
||||||
return fmt.Sprintf("char(%d)", ti.Size)
|
return fmt.Sprintf("char(%d)", ti.Size)
|
||||||
case typeBigVarChar, typeVarChar:
|
case typeBigVarChar, typeVarChar:
|
||||||
if ti.Size > 4000 || ti.Size == 0 {
|
if ti.Size > 8000 || ti.Size == 0 {
|
||||||
return fmt.Sprintf("varchar(max)")
|
return fmt.Sprintf("varchar(max)")
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("varchar(%d)", ti.Size)
|
return fmt.Sprintf("varchar(%d)", ti.Size)
|
||||||
|
|
|
@ -72,3 +72,9 @@ func (u UniqueIdentifier) Value() (driver.Value, error) {
|
||||||
func (u UniqueIdentifier) String() string {
|
func (u UniqueIdentifier) String() string {
|
||||||
return fmt.Sprintf("%X-%X-%X-%X-%X", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
|
return fmt.Sprintf("%X-%X-%X-%X-%X", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalText converts Uniqueidentifier to bytes corresponding to the stringified hexadecimal representation of the Uniqueidentifier
|
||||||
|
// e.g., "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA" -> [65 65 65 65 65 65 65 65 45 65 65 65 65 45 65 65 65 65 45 65 65 65 65 65 65 65 65 65 65 65 65]
|
||||||
|
func (u UniqueIdentifier) MarshalText() []byte {
|
||||||
|
return []byte(u.String())
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,10 +0,0 @@
|
||||||
.PHONY: ci generate clean
|
|
||||||
|
|
||||||
ci: clean generate
|
|
||||||
go test -v ./...
|
|
||||||
|
|
||||||
generate:
|
|
||||||
go generate .
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf *_generated*.go
|
|
|
@ -1,94 +0,0 @@
|
||||||
# httpsnoop
|
|
||||||
|
|
||||||
Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
|
||||||
response time, bytes written, and http status code) from your application's
|
|
||||||
http.Handlers.
|
|
||||||
|
|
||||||
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
|
|
||||||
which is also exposed for users interested in a more low-level API.
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/felixge/httpsnoop?status.svg)](https://godoc.org/github.com/felixge/httpsnoop)
|
|
||||||
[![Build Status](https://travis-ci.org/felixge/httpsnoop.svg?branch=master)](https://travis-ci.org/felixge/httpsnoop)
|
|
||||||
|
|
||||||
## Usage Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
// myH is your app's http handler, perhaps a http.ServeMux or similar.
|
|
||||||
var myH http.Handler
|
|
||||||
// wrappedH wraps myH in order to log every request.
|
|
||||||
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
m := httpsnoop.CaptureMetrics(myH, w, r)
|
|
||||||
log.Printf(
|
|
||||||
"%s %s (code=%d dt=%s written=%d)",
|
|
||||||
r.Method,
|
|
||||||
r.URL,
|
|
||||||
m.Code,
|
|
||||||
m.Duration,
|
|
||||||
m.Written,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
http.ListenAndServe(":8080", wrappedH)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why this package exists
|
|
||||||
|
|
||||||
Instrumenting an application's http.Handler is surprisingly difficult.
|
|
||||||
|
|
||||||
However if you google for e.g. "capture ResponseWriter status code" you'll find
|
|
||||||
lots of advise and code examples that suggest it to be a fairly trivial
|
|
||||||
undertaking. Unfortunately everything I've seen so far has a high chance of
|
|
||||||
breaking your application.
|
|
||||||
|
|
||||||
The main problem is that a `http.ResponseWriter` often implements additional
|
|
||||||
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
|
|
||||||
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
|
|
||||||
in your own struct that also implements the `http.ResponseWriter` interface
|
|
||||||
will hide the additional interfaces mentioned above. This has a high change of
|
|
||||||
introducing subtle bugs into any non-trivial application.
|
|
||||||
|
|
||||||
Another approach I've seen people take is to return a struct that implements
|
|
||||||
all of the interfaces above. However, that's also problematic, because it's
|
|
||||||
difficult to fake some of these interfaces behaviors when the underlying
|
|
||||||
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
|
|
||||||
because an application may choose to operate differently, merely because it
|
|
||||||
detects the presence of these additional interfaces.
|
|
||||||
|
|
||||||
This package solves this problem by checking which additional interfaces a
|
|
||||||
`http.ResponseWriter` implements, returning a wrapped version implementing the
|
|
||||||
exact same set of interfaces.
|
|
||||||
|
|
||||||
Additionally this package properly handles edge cases such as `WriteHeader` not
|
|
||||||
being called, or called more than once, as well as concurrent calls to
|
|
||||||
`http.ResponseWriter` methods, and even calls happening after the wrapped
|
|
||||||
`ServeHTTP` has already returned.
|
|
||||||
|
|
||||||
Unfortunately this package is not perfect either. It's possible that it is
|
|
||||||
still missing some interfaces provided by the go core (let me know if you find
|
|
||||||
one), and it won't work for applications adding their own interfaces into the
|
|
||||||
mix.
|
|
||||||
|
|
||||||
However, hopefully the explanation above has sufficiently scared you of rolling
|
|
||||||
your own solution to this problem. httpsnoop may still break your application,
|
|
||||||
but at least it tries to avoid it as much as possible.
|
|
||||||
|
|
||||||
Anyway, the real problem here is that smuggling additional interfaces inside
|
|
||||||
`http.ResponseWriter` is a problematic design choice, but it probably goes as
|
|
||||||
deep as the Go language specification itself. But that's okay, I still prefer
|
|
||||||
Go over the alternatives ;).
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
```
|
|
||||||
BenchmarkBaseline-8 20000 94912 ns/op
|
|
||||||
BenchmarkCaptureMetrics-8 20000 95461 ns/op
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
|
|
||||||
overhead of ~500 ns per http request on my machine. However, the margin of
|
|
||||||
error appears to be larger than that, therefor it should be reasonable to
|
|
||||||
assume that the overhead introduced by `CaptureMetrics` is absolutely
|
|
||||||
negligible.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
|
@ -1,84 +0,0 @@
|
||||||
package httpsnoop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Metrics holds metrics captured from CaptureMetrics.
|
|
||||||
type Metrics struct {
|
|
||||||
// Code is the first http response code passed to the WriteHeader func of
|
|
||||||
// the ResponseWriter. If no such call is made, a default code of 200 is
|
|
||||||
// assumed instead.
|
|
||||||
Code int
|
|
||||||
// Duration is the time it took to execute the handler.
|
|
||||||
Duration time.Duration
|
|
||||||
// Written is the number of bytes successfully written by the Write or
|
|
||||||
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
|
|
||||||
// data to their underlaying connection directly (e.g. headers), but those
|
|
||||||
// are not tracked. Therefor the number of Written bytes will usually match
|
|
||||||
// the size of the response body.
|
|
||||||
Written int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
|
|
||||||
// returns the metrics it captured from it.
|
|
||||||
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
|
|
||||||
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
|
|
||||||
hnd.ServeHTTP(ww, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
|
|
||||||
// resulting metrics. This is very similar to CaptureMetrics (which is just
|
|
||||||
// sugar on top of this func), but is a more usable interface if your
|
|
||||||
// application doesn't use the Go http.Handler interface.
|
|
||||||
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
|
|
||||||
var (
|
|
||||||
start = time.Now()
|
|
||||||
m = Metrics{Code: http.StatusOK}
|
|
||||||
headerWritten bool
|
|
||||||
lock sync.Mutex
|
|
||||||
hooks = Hooks{
|
|
||||||
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
|
|
||||||
return func(code int) {
|
|
||||||
next(code)
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
if !headerWritten {
|
|
||||||
m.Code = code
|
|
||||||
headerWritten = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Write: func(next WriteFunc) WriteFunc {
|
|
||||||
return func(p []byte) (int, error) {
|
|
||||||
n, err := next(p)
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
m.Written += int64(n)
|
|
||||||
headerWritten = true
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
|
|
||||||
return func(src io.Reader) (int64, error) {
|
|
||||||
n, err := next(src)
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
headerWritten = true
|
|
||||||
m.Written += n
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fn(Wrap(w, hooks))
|
|
||||||
m.Duration = time.Since(start)
|
|
||||||
return m
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
|
||||||
// response time, bytes written, and http status code) from your application's
|
|
||||||
// http.Handlers.
|
|
||||||
//
|
|
||||||
// Doing this requires non-trivial wrapping of the http.ResponseWriter
|
|
||||||
// interface, which is also exposed for users interested in a more low-level
|
|
||||||
// API.
|
|
||||||
package httpsnoop
|
|
||||||
|
|
||||||
//go:generate go run codegen/main.go
|
|
|
@ -1,385 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
|
||||||
|
|
||||||
package httpsnoop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
|
||||||
type HeaderFunc func() http.Header
|
|
||||||
|
|
||||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
|
||||||
type WriteHeaderFunc func(code int)
|
|
||||||
|
|
||||||
// WriteFunc is part of the http.ResponseWriter interface.
|
|
||||||
type WriteFunc func(b []byte) (int, error)
|
|
||||||
|
|
||||||
// FlushFunc is part of the http.Flusher interface.
|
|
||||||
type FlushFunc func()
|
|
||||||
|
|
||||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
|
||||||
type CloseNotifyFunc func() <-chan bool
|
|
||||||
|
|
||||||
// HijackFunc is part of the http.Hijacker interface.
|
|
||||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
|
||||||
|
|
||||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
|
||||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
|
||||||
|
|
||||||
// PushFunc is part of the http.Pusher interface.
|
|
||||||
type PushFunc func(target string, opts *http.PushOptions) error
|
|
||||||
|
|
||||||
// Hooks defines a set of method interceptors for methods included in
|
|
||||||
// http.ResponseWriter as well as some others. You can think of them as
|
|
||||||
// middleware for the function calls they target. See Wrap for more details.
|
|
||||||
type Hooks struct {
|
|
||||||
Header func(HeaderFunc) HeaderFunc
|
|
||||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
|
||||||
Write func(WriteFunc) WriteFunc
|
|
||||||
Flush func(FlushFunc) FlushFunc
|
|
||||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
|
||||||
Hijack func(HijackFunc) HijackFunc
|
|
||||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
|
||||||
Push func(PushFunc) PushFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
|
||||||
// as w. Specifically if w implements any combination of:
|
|
||||||
//
|
|
||||||
// - http.Flusher
|
|
||||||
// - http.CloseNotifier
|
|
||||||
// - http.Hijacker
|
|
||||||
// - io.ReaderFrom
|
|
||||||
// - http.Pusher
|
|
||||||
//
|
|
||||||
// The wrapped version will implement the exact same combination. If no hooks
|
|
||||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
|
||||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
|
||||||
// method they target and may modify the call's arguments and/or return values.
|
|
||||||
// The CaptureMetrics implementation serves as a working example for how the
|
|
||||||
// hooks can be used.
|
|
||||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
|
||||||
rw := &rw{w: w, h: hooks}
|
|
||||||
_, i0 := w.(http.Flusher)
|
|
||||||
_, i1 := w.(http.CloseNotifier)
|
|
||||||
_, i2 := w.(http.Hijacker)
|
|
||||||
_, i3 := w.(io.ReaderFrom)
|
|
||||||
_, i4 := w.(http.Pusher)
|
|
||||||
switch {
|
|
||||||
// combination 1/32
|
|
||||||
case !i0 && !i1 && !i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
}{rw}
|
|
||||||
// combination 2/32
|
|
||||||
case !i0 && !i1 && !i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 3/32
|
|
||||||
case !i0 && !i1 && !i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 4/32
|
|
||||||
case !i0 && !i1 && !i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 5/32
|
|
||||||
case !i0 && !i1 && i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 6/32
|
|
||||||
case !i0 && !i1 && i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 7/32
|
|
||||||
case !i0 && !i1 && i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 8/32
|
|
||||||
case !i0 && !i1 && i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 9/32
|
|
||||||
case !i0 && i1 && !i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 10/32
|
|
||||||
case !i0 && i1 && !i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 11/32
|
|
||||||
case !i0 && i1 && !i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 12/32
|
|
||||||
case !i0 && i1 && !i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 13/32
|
|
||||||
case !i0 && i1 && i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 14/32
|
|
||||||
case !i0 && i1 && i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 15/32
|
|
||||||
case !i0 && i1 && i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 16/32
|
|
||||||
case !i0 && i1 && i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
// combination 17/32
|
|
||||||
case i0 && !i1 && !i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 18/32
|
|
||||||
case i0 && !i1 && !i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 19/32
|
|
||||||
case i0 && !i1 && !i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 20/32
|
|
||||||
case i0 && !i1 && !i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 21/32
|
|
||||||
case i0 && !i1 && i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 22/32
|
|
||||||
case i0 && !i1 && i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 23/32
|
|
||||||
case i0 && !i1 && i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 24/32
|
|
||||||
case i0 && !i1 && i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
// combination 25/32
|
|
||||||
case i0 && i1 && !i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 26/32
|
|
||||||
case i0 && i1 && !i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 27/32
|
|
||||||
case i0 && i1 && !i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 28/32
|
|
||||||
case i0 && i1 && !i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
// combination 29/32
|
|
||||||
case i0 && i1 && i2 && !i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 30/32
|
|
||||||
case i0 && i1 && i2 && !i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
// combination 31/32
|
|
||||||
case i0 && i1 && i2 && i3 && !i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
// combination 32/32
|
|
||||||
case i0 && i1 && i2 && i3 && i4:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
http.Pusher
|
|
||||||
}{rw, rw, rw, rw, rw, rw}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
type rw struct {
|
|
||||||
w http.ResponseWriter
|
|
||||||
h Hooks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Header() http.Header {
|
|
||||||
f := w.w.(http.ResponseWriter).Header
|
|
||||||
if w.h.Header != nil {
|
|
||||||
f = w.h.Header(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) WriteHeader(code int) {
|
|
||||||
f := w.w.(http.ResponseWriter).WriteHeader
|
|
||||||
if w.h.WriteHeader != nil {
|
|
||||||
f = w.h.WriteHeader(f)
|
|
||||||
}
|
|
||||||
f(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Write(b []byte) (int, error) {
|
|
||||||
f := w.w.(http.ResponseWriter).Write
|
|
||||||
if w.h.Write != nil {
|
|
||||||
f = w.h.Write(f)
|
|
||||||
}
|
|
||||||
return f(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Flush() {
|
|
||||||
f := w.w.(http.Flusher).Flush
|
|
||||||
if w.h.Flush != nil {
|
|
||||||
f = w.h.Flush(f)
|
|
||||||
}
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) CloseNotify() <-chan bool {
|
|
||||||
f := w.w.(http.CloseNotifier).CloseNotify
|
|
||||||
if w.h.CloseNotify != nil {
|
|
||||||
f = w.h.CloseNotify(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
f := w.w.(http.Hijacker).Hijack
|
|
||||||
if w.h.Hijack != nil {
|
|
||||||
f = w.h.Hijack(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
|
||||||
f := w.w.(io.ReaderFrom).ReadFrom
|
|
||||||
if w.h.ReadFrom != nil {
|
|
||||||
f = w.h.ReadFrom(f)
|
|
||||||
}
|
|
||||||
return f(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Push(target string, opts *http.PushOptions) error {
|
|
||||||
f := w.w.(http.Pusher).Push
|
|
||||||
if w.h.Push != nil {
|
|
||||||
f = w.h.Push(f)
|
|
||||||
}
|
|
||||||
return f(target, opts)
|
|
||||||
}
|
|
|
@ -1,243 +0,0 @@
|
||||||
// +build !go1.8
|
|
||||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
|
||||||
|
|
||||||
package httpsnoop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
|
||||||
type HeaderFunc func() http.Header
|
|
||||||
|
|
||||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
|
||||||
type WriteHeaderFunc func(code int)
|
|
||||||
|
|
||||||
// WriteFunc is part of the http.ResponseWriter interface.
|
|
||||||
type WriteFunc func(b []byte) (int, error)
|
|
||||||
|
|
||||||
// FlushFunc is part of the http.Flusher interface.
|
|
||||||
type FlushFunc func()
|
|
||||||
|
|
||||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
|
||||||
type CloseNotifyFunc func() <-chan bool
|
|
||||||
|
|
||||||
// HijackFunc is part of the http.Hijacker interface.
|
|
||||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
|
||||||
|
|
||||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
|
||||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
|
||||||
|
|
||||||
// Hooks defines a set of method interceptors for methods included in
|
|
||||||
// http.ResponseWriter as well as some others. You can think of them as
|
|
||||||
// middleware for the function calls they target. See Wrap for more details.
|
|
||||||
type Hooks struct {
|
|
||||||
Header func(HeaderFunc) HeaderFunc
|
|
||||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
|
||||||
Write func(WriteFunc) WriteFunc
|
|
||||||
Flush func(FlushFunc) FlushFunc
|
|
||||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
|
||||||
Hijack func(HijackFunc) HijackFunc
|
|
||||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
|
||||||
// as w. Specifically if w implements any combination of:
|
|
||||||
//
|
|
||||||
// - http.Flusher
|
|
||||||
// - http.CloseNotifier
|
|
||||||
// - http.Hijacker
|
|
||||||
// - io.ReaderFrom
|
|
||||||
//
|
|
||||||
// The wrapped version will implement the exact same combination. If no hooks
|
|
||||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
|
||||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
|
||||||
// method they target and may modify the call's arguments and/or return values.
|
|
||||||
// The CaptureMetrics implementation serves as a working example for how the
|
|
||||||
// hooks can be used.
|
|
||||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
|
||||||
rw := &rw{w: w, h: hooks}
|
|
||||||
_, i0 := w.(http.Flusher)
|
|
||||||
_, i1 := w.(http.CloseNotifier)
|
|
||||||
_, i2 := w.(http.Hijacker)
|
|
||||||
_, i3 := w.(io.ReaderFrom)
|
|
||||||
switch {
|
|
||||||
// combination 1/16
|
|
||||||
case !i0 && !i1 && !i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
}{rw}
|
|
||||||
// combination 2/16
|
|
||||||
case !i0 && !i1 && !i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 3/16
|
|
||||||
case !i0 && !i1 && i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 4/16
|
|
||||||
case !i0 && !i1 && i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 5/16
|
|
||||||
case !i0 && i1 && !i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 6/16
|
|
||||||
case !i0 && i1 && !i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 7/16
|
|
||||||
case !i0 && i1 && i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 8/16
|
|
||||||
case !i0 && i1 && i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 9/16
|
|
||||||
case i0 && !i1 && !i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
}{rw, rw}
|
|
||||||
// combination 10/16
|
|
||||||
case i0 && !i1 && !i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 11/16
|
|
||||||
case i0 && !i1 && i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 12/16
|
|
||||||
case i0 && !i1 && i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 13/16
|
|
||||||
case i0 && i1 && !i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
}{rw, rw, rw}
|
|
||||||
// combination 14/16
|
|
||||||
case i0 && i1 && !i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 15/16
|
|
||||||
case i0 && i1 && i2 && !i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
}{rw, rw, rw, rw}
|
|
||||||
// combination 16/16
|
|
||||||
case i0 && i1 && i2 && i3:
|
|
||||||
return struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
http.Flusher
|
|
||||||
http.CloseNotifier
|
|
||||||
http.Hijacker
|
|
||||||
io.ReaderFrom
|
|
||||||
}{rw, rw, rw, rw, rw}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
type rw struct {
|
|
||||||
w http.ResponseWriter
|
|
||||||
h Hooks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Header() http.Header {
|
|
||||||
f := w.w.(http.ResponseWriter).Header
|
|
||||||
if w.h.Header != nil {
|
|
||||||
f = w.h.Header(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) WriteHeader(code int) {
|
|
||||||
f := w.w.(http.ResponseWriter).WriteHeader
|
|
||||||
if w.h.WriteHeader != nil {
|
|
||||||
f = w.h.WriteHeader(f)
|
|
||||||
}
|
|
||||||
f(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Write(b []byte) (int, error) {
|
|
||||||
f := w.w.(http.ResponseWriter).Write
|
|
||||||
if w.h.Write != nil {
|
|
||||||
f = w.h.Write(f)
|
|
||||||
}
|
|
||||||
return f(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Flush() {
|
|
||||||
f := w.w.(http.Flusher).Flush
|
|
||||||
if w.h.Flush != nil {
|
|
||||||
f = w.h.Flush(f)
|
|
||||||
}
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) CloseNotify() <-chan bool {
|
|
||||||
f := w.w.(http.CloseNotifier).CloseNotify
|
|
||||||
if w.h.CloseNotify != nil {
|
|
||||||
f = w.h.CloseNotify(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
f := w.w.(http.Hijacker).Hijack
|
|
||||||
if w.h.Hijack != nil {
|
|
||||||
f = w.h.Hijack(f)
|
|
||||||
}
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
|
||||||
f := w.w.(io.ReaderFrom).ReadFrom
|
|
||||||
if w.h.ReadFrom != nil {
|
|
||||||
f = w.h.ReadFrom(f)
|
|
||||||
}
|
|
||||||
return f(src)
|
|
||||||
}
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
1. Sign one of the contributor license agreements below.
|
||||||
|
|
||||||
|
#### Running
|
||||||
|
|
||||||
|
Once you've done the necessary setup, you can run the integration tests by
|
||||||
|
running:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ go test -v github.com/golang-sql/civil
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributor License Agreements
|
||||||
|
|
||||||
|
Before we can accept your pull requests you'll need to sign a Contributor
|
||||||
|
License Agreement (CLA):
|
||||||
|
|
||||||
|
- **If you are an individual writing original source code** and **you own the
|
||||||
|
intellectual property**, then you'll need to sign an [individual CLA][indvcla].
|
||||||
|
- **If you work for a company that wants to allow you to contribute your
|
||||||
|
work**, then you'll need to sign a [corporate CLA][corpcla].
|
||||||
|
|
||||||
|
You can sign these electronically (just scroll to the bottom). After that,
|
||||||
|
we'll be able to accept your pull requests.
|
||||||
|
|
||||||
|
## Contributor Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project,
|
||||||
|
and in the interest of fostering an open and welcoming community,
|
||||||
|
we pledge to respect all people who contribute through reporting issues,
|
||||||
|
posting feature requests, updating documentation,
|
||||||
|
submitting pull requests or patches, and other activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project
|
||||||
|
a harassment-free experience for everyone,
|
||||||
|
regardless of level of experience, gender, gender identity and expression,
|
||||||
|
sexual orientation, disability, personal appearance,
|
||||||
|
body size, race, ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery
|
||||||
|
* Personal attacks
|
||||||
|
* Trolling or insulting/derogatory comments
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing other's private information,
|
||||||
|
such as physical or electronic
|
||||||
|
addresses, without explicit permission
|
||||||
|
* Other unethical or unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct.
|
||||||
|
By adopting this Code of Conduct,
|
||||||
|
project maintainers commit themselves to fairly and consistently
|
||||||
|
applying these principles to every aspect of managing this project.
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct
|
||||||
|
may be permanently removed from the project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||||
|
may be reported by opening an issue
|
||||||
|
or contacting one or more of the project maintainers.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||||
|
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||||
|
|
||||||
|
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
|
||||||
|
[indvcla]: https://developers.google.com/open-source/cla/individual
|
||||||
|
[corpcla]: https://developers.google.com/open-source/cla/corporate
|
0
vendor/cloud.google.com/go/LICENSE → vendor/github.com/golang-sql/civil/LICENSE
generated
vendored
0
vendor/cloud.google.com/go/LICENSE → vendor/github.com/golang-sql/civil/LICENSE
generated
vendored
|
@ -0,0 +1,15 @@
|
||||||
|
# Civil Date and Time
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/golang-sql/civil?status.svg)](https://godoc.org/github.com/golang-sql/civil)
|
||||||
|
|
||||||
|
Civil provides Date, Time of Day, and DateTime data types.
|
||||||
|
|
||||||
|
While there are many uses, using specific types when working
|
||||||
|
with databases make is conceptually eaiser to understand what value
|
||||||
|
is set in the remote system.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
This civil package was extracted and forked from `cloud.google.com/go/civil`.
|
||||||
|
As such the license and contributing requirements remain the same as that
|
||||||
|
module.
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2013 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2013 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,298 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file or at
|
|
||||||
// https://developers.google.com/open-source/licenses/bsd.
|
|
||||||
|
|
||||||
// Package header provides functions for parsing HTTP headers.
|
|
||||||
package header
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Octet types from RFC 2616.
|
|
||||||
var octetTypes [256]octetType
|
|
||||||
|
|
||||||
type octetType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
isToken octetType = 1 << iota
|
|
||||||
isSpace
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// OCTET = <any 8-bit sequence of data>
|
|
||||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
|
||||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
|
||||||
// CR = <US-ASCII CR, carriage return (13)>
|
|
||||||
// LF = <US-ASCII LF, linefeed (10)>
|
|
||||||
// SP = <US-ASCII SP, space (32)>
|
|
||||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
|
||||||
// <"> = <US-ASCII double-quote mark (34)>
|
|
||||||
// CRLF = CR LF
|
|
||||||
// LWS = [CRLF] 1*( SP | HT )
|
|
||||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
|
||||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
|
||||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
|
||||||
// token = 1*<any CHAR except CTLs or separators>
|
|
||||||
// qdtext = <any TEXT except <">>
|
|
||||||
|
|
||||||
for c := 0; c < 256; c++ {
|
|
||||||
var t octetType
|
|
||||||
isCtl := c <= 31 || c == 127
|
|
||||||
isChar := 0 <= c && c <= 127
|
|
||||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
|
||||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
|
||||||
t |= isSpace
|
|
||||||
}
|
|
||||||
if isChar && !isCtl && !isSeparator {
|
|
||||||
t |= isToken
|
|
||||||
}
|
|
||||||
octetTypes[c] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a shallow copy of the header.
|
|
||||||
func Copy(header http.Header) http.Header {
|
|
||||||
h := make(http.Header)
|
|
||||||
for k, vs := range header {
|
|
||||||
h[k] = vs
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
|
|
||||||
|
|
||||||
// ParseTime parses the header as time. The zero value is returned if the
|
|
||||||
// header is not present or there is an error parsing the
|
|
||||||
// header.
|
|
||||||
func ParseTime(header http.Header, key string) time.Time {
|
|
||||||
if s := header.Get(key); s != "" {
|
|
||||||
for _, layout := range timeLayouts {
|
|
||||||
if t, err := time.Parse(layout, s); err == nil {
|
|
||||||
return t.UTC()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseList parses a comma separated list of values. Commas are ignored in
|
|
||||||
// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
|
|
||||||
// trimmed.
|
|
||||||
func ParseList(header http.Header, key string) []string {
|
|
||||||
var result []string
|
|
||||||
for _, s := range header[http.CanonicalHeaderKey(key)] {
|
|
||||||
begin := 0
|
|
||||||
end := 0
|
|
||||||
escape := false
|
|
||||||
quote := false
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
b := s[i]
|
|
||||||
switch {
|
|
||||||
case escape:
|
|
||||||
escape = false
|
|
||||||
end = i + 1
|
|
||||||
case quote:
|
|
||||||
switch b {
|
|
||||||
case '\\':
|
|
||||||
escape = true
|
|
||||||
case '"':
|
|
||||||
quote = false
|
|
||||||
}
|
|
||||||
end = i + 1
|
|
||||||
case b == '"':
|
|
||||||
quote = true
|
|
||||||
end = i + 1
|
|
||||||
case octetTypes[b]&isSpace != 0:
|
|
||||||
if begin == end {
|
|
||||||
begin = i + 1
|
|
||||||
end = begin
|
|
||||||
}
|
|
||||||
case b == ',':
|
|
||||||
if begin < end {
|
|
||||||
result = append(result, s[begin:end])
|
|
||||||
}
|
|
||||||
begin = i + 1
|
|
||||||
end = begin
|
|
||||||
default:
|
|
||||||
end = i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if begin < end {
|
|
||||||
result = append(result, s[begin:end])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseValueAndParams parses a comma separated list of values with optional
|
|
||||||
// semicolon separated name-value pairs. Content-Type and Content-Disposition
|
|
||||||
// headers are in this format.
|
|
||||||
func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
|
|
||||||
params = make(map[string]string)
|
|
||||||
s := header.Get(key)
|
|
||||||
value, s = expectTokenSlash(s)
|
|
||||||
if value == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = strings.ToLower(value)
|
|
||||||
s = skipSpace(s)
|
|
||||||
for strings.HasPrefix(s, ";") {
|
|
||||||
var pkey string
|
|
||||||
pkey, s = expectToken(skipSpace(s[1:]))
|
|
||||||
if pkey == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(s, "=") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var pvalue string
|
|
||||||
pvalue, s = expectTokenOrQuoted(s[1:])
|
|
||||||
if pvalue == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pkey = strings.ToLower(pkey)
|
|
||||||
params[pkey] = pvalue
|
|
||||||
s = skipSpace(s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptSpec describes an Accept* header.
|
|
||||||
type AcceptSpec struct {
|
|
||||||
Value string
|
|
||||||
Q float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAccept parses Accept* headers.
|
|
||||||
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
|
|
||||||
loop:
|
|
||||||
for _, s := range header[key] {
|
|
||||||
for {
|
|
||||||
var spec AcceptSpec
|
|
||||||
spec.Value, s = expectTokenSlash(s)
|
|
||||||
if spec.Value == "" {
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
spec.Q = 1.0
|
|
||||||
s = skipSpace(s)
|
|
||||||
if strings.HasPrefix(s, ";") {
|
|
||||||
s = skipSpace(s[1:])
|
|
||||||
if !strings.HasPrefix(s, "q=") {
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
spec.Q, s = expectQuality(s[2:])
|
|
||||||
if spec.Q < 0.0 {
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
specs = append(specs, spec)
|
|
||||||
s = skipSpace(s)
|
|
||||||
if !strings.HasPrefix(s, ",") {
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
s = skipSpace(s[1:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipSpace(s string) (rest string) {
|
|
||||||
i := 0
|
|
||||||
for ; i < len(s); i++ {
|
|
||||||
if octetTypes[s[i]]&isSpace == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectToken(s string) (token, rest string) {
|
|
||||||
i := 0
|
|
||||||
for ; i < len(s); i++ {
|
|
||||||
if octetTypes[s[i]]&isToken == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[:i], s[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectTokenSlash(s string) (token, rest string) {
|
|
||||||
i := 0
|
|
||||||
for ; i < len(s); i++ {
|
|
||||||
b := s[i]
|
|
||||||
if (octetTypes[b]&isToken == 0) && b != '/' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[:i], s[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectQuality(s string) (q float64, rest string) {
|
|
||||||
switch {
|
|
||||||
case len(s) == 0:
|
|
||||||
return -1, ""
|
|
||||||
case s[0] == '0':
|
|
||||||
q = 0
|
|
||||||
case s[0] == '1':
|
|
||||||
q = 1
|
|
||||||
default:
|
|
||||||
return -1, ""
|
|
||||||
}
|
|
||||||
s = s[1:]
|
|
||||||
if !strings.HasPrefix(s, ".") {
|
|
||||||
return q, s
|
|
||||||
}
|
|
||||||
s = s[1:]
|
|
||||||
i := 0
|
|
||||||
n := 0
|
|
||||||
d := 1
|
|
||||||
for ; i < len(s); i++ {
|
|
||||||
b := s[i]
|
|
||||||
if b < '0' || b > '9' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n = n*10 + int(b) - '0'
|
|
||||||
d *= 10
|
|
||||||
}
|
|
||||||
return q + float64(n)/float64(d), s[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
|
||||||
if !strings.HasPrefix(s, "\"") {
|
|
||||||
return expectToken(s)
|
|
||||||
}
|
|
||||||
s = s[1:]
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch s[i] {
|
|
||||||
case '"':
|
|
||||||
return s[:i], s[i+1:]
|
|
||||||
case '\\':
|
|
||||||
p := make([]byte, len(s)-1)
|
|
||||||
j := copy(p, s[:i])
|
|
||||||
escape := true
|
|
||||||
for i = i + 1; i < len(s); i++ {
|
|
||||||
b := s[i]
|
|
||||||
switch {
|
|
||||||
case escape:
|
|
||||||
escape = false
|
|
||||||
p[j] = b
|
|
||||||
j++
|
|
||||||
case b == '\\':
|
|
||||||
escape = true
|
|
||||||
case b == '"':
|
|
||||||
return string(p[:j]), s[i+1:]
|
|
||||||
default:
|
|
||||||
p[j] = b
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", ""
|
|
||||||
}
|
|
|
@ -3,11 +3,11 @@ sudo: false
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- go: 1.4
|
- go: 1.7.x
|
||||||
- go: 1.5
|
- go: 1.8.x
|
||||||
- go: 1.6
|
- go: 1.9.x
|
||||||
- go: 1.7
|
- go: 1.10.x
|
||||||
- go: 1.8
|
- go: 1.11.x
|
||||||
- go: tip
|
- go: tip
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- go: tip
|
- go: tip
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
# Please keep the list sorted.
|
# Please keep the list sorted.
|
||||||
|
|
||||||
Gary Burd <gary@beagledreams.com>
|
Gary Burd <gary@beagledreams.com>
|
||||||
|
Google LLC (https://opensource.google.com/)
|
||||||
Joachim Bauch <mail@joachim-bauch.de>
|
Joachim Bauch <mail@joachim-bauch.de>
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,15 @@
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -53,6 +53,10 @@ type Dialer struct {
|
||||||
// NetDial is nil, net.Dial is used.
|
// NetDial is nil, net.Dial is used.
|
||||||
NetDial func(network, addr string) (net.Conn, error)
|
NetDial func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||||
|
// NetDialContext is nil, net.DialContext is used.
|
||||||
|
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
// Proxy specifies a function to return a proxy for a given
|
// Proxy specifies a function to return a proxy for a given
|
||||||
// Request. If the function returns a non-nil error, the
|
// Request. If the function returns a non-nil error, the
|
||||||
// request is aborted with the provided error.
|
// request is aborted with the provided error.
|
||||||
|
@ -71,6 +75,17 @@ type Dialer struct {
|
||||||
// do not limit the size of the messages that can be sent or received.
|
// do not limit the size of the messages that can be sent or received.
|
||||||
ReadBufferSize, WriteBufferSize int
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
// Subprotocols specifies the client's requested subprotocols.
|
// Subprotocols specifies the client's requested subprotocols.
|
||||||
Subprotocols []string
|
Subprotocols []string
|
||||||
|
|
||||||
|
@ -86,52 +101,13 @@ type Dialer struct {
|
||||||
Jar http.CookieJar
|
Jar http.CookieJar
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
// Dial creates a new client connection by calling DialContext with a background context.
|
||||||
|
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
// parseURL parses the URL.
|
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||||
//
|
|
||||||
// This function is a replacement for the standard library url.Parse function.
|
|
||||||
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
|
||||||
func parseURL(s string) (*url.URL, error) {
|
|
||||||
// From the RFC:
|
|
||||||
//
|
|
||||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
|
||||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
|
||||||
var u url.URL
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(s, "ws://"):
|
|
||||||
u.Scheme = "ws"
|
|
||||||
s = s[len("ws://"):]
|
|
||||||
case strings.HasPrefix(s, "wss://"):
|
|
||||||
u.Scheme = "wss"
|
|
||||||
s = s[len("wss://"):]
|
|
||||||
default:
|
|
||||||
return nil, errMalformedURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := strings.Index(s, "?"); i >= 0 {
|
|
||||||
u.RawQuery = s[i+1:]
|
|
||||||
s = s[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := strings.Index(s, "/"); i >= 0 {
|
|
||||||
u.Opaque = s[i:]
|
|
||||||
s = s[:i]
|
|
||||||
} else {
|
|
||||||
u.Opaque = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
u.Host = s
|
|
||||||
|
|
||||||
if strings.Contains(u.Host, "@") {
|
|
||||||
// Don't bother parsing user information because user information is
|
|
||||||
// not allowed in websocket URIs.
|
|
||||||
return nil, errMalformedURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return &u, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
|
|
||||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||||
hostPort = u.Host
|
hostPort = u.Host
|
||||||
hostNoPort = u.Host
|
hostNoPort = u.Host
|
||||||
|
@ -150,26 +126,29 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||||
return hostPort, hostNoPort
|
return hostPort, hostNoPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
// DefaultDialer is a dialer with all fields set to the default values.
|
||||||
var DefaultDialer = &Dialer{
|
var DefaultDialer = &Dialer{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
HandshakeTimeout: 45 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial creates a new client connection. Use requestHeader to specify the
|
// nilDialer is dialer to use when receiver is nil.
|
||||||
|
var nilDialer = *DefaultDialer
|
||||||
|
|
||||||
|
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||||
// Use the response.Header to get the selected subprotocol
|
// Use the response.Header to get the selected subprotocol
|
||||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||||
//
|
//
|
||||||
|
// The context will be used in the request and in the Dialer
|
||||||
|
//
|
||||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
// etcetera. The response body may not contain the entire response and does not
|
// etcetera. The response body may not contain the entire response and does not
|
||||||
// need to be closed by the application.
|
// need to be closed by the application.
|
||||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
|
||||||
if d == nil {
|
if d == nil {
|
||||||
d = &Dialer{
|
d = &nilDialer
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeKey, err := generateChallengeKey()
|
challengeKey, err := generateChallengeKey()
|
||||||
|
@ -177,7 +156,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := parseURL(urlStr)
|
u, err := url.Parse(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -205,6 +184,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
Host: u.Host,
|
Host: u.Host,
|
||||||
}
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
// Set the cookies present in the cookie jar of the dialer
|
// Set the cookies present in the cookie jar of the dialer
|
||||||
if d.Jar != nil {
|
if d.Jar != nil {
|
||||||
|
@ -237,45 +217,83 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
k == "Sec-Websocket-Extensions" ||
|
k == "Sec-Websocket-Extensions" ||
|
||||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
|
case k == "Sec-Websocket-Protocol":
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||||
default:
|
default:
|
||||||
req.Header[k] = vs
|
req.Header[k] = vs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.EnableCompression {
|
if d.EnableCompression {
|
||||||
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HandshakeTimeout != 0 {
|
||||||
|
var cancel func()
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get network dial function.
|
||||||
|
var netDial func(network, add string) (net.Conn, error)
|
||||||
|
|
||||||
|
if d.NetDialContext != nil {
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return d.NetDialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
} else if d.NetDial != nil {
|
||||||
|
netDial = d.NetDial
|
||||||
|
} else {
|
||||||
|
netDialer := &net.Dialer{}
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
return netDialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to set the connection deadline.
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
forwardDial := netDial
|
||||||
|
netDial = func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := forwardDial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = c.SetDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, wrap the dial function to connect through a proxy.
|
||||||
|
if d.Proxy != nil {
|
||||||
|
proxyURL, err := d.Proxy(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if proxyURL != nil {
|
||||||
|
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
netDial = dialer.Dial
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort, hostNoPort := hostPortNoPort(u)
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
trace := httptrace.ContextClientTrace(ctx)
|
||||||
var proxyURL *url.URL
|
if trace != nil && trace.GetConn != nil {
|
||||||
// Check wether the proxy method has been configured
|
trace.GetConn(hostPort)
|
||||||
if d.Proxy != nil {
|
|
||||||
proxyURL, err = d.Proxy(req)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetHostPort string
|
netConn, err := netDial("tcp", hostPort)
|
||||||
if proxyURL != nil {
|
if trace != nil && trace.GotConn != nil {
|
||||||
targetHostPort, _ = hostPortNoPort(proxyURL)
|
trace.GotConn(httptrace.GotConnInfo{
|
||||||
} else {
|
Conn: netConn,
|
||||||
targetHostPort = hostPort
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadline time.Time
|
|
||||||
if d.HandshakeTimeout != 0 {
|
|
||||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
netDial := d.NetDial
|
|
||||||
if netDial == nil {
|
|
||||||
netDialer := &net.Dialer{Deadline: deadline}
|
|
||||||
netDial = netDialer.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
netConn, err := netDial("tcp", targetHostPort)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -286,42 +304,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := netConn.SetDeadline(deadline); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxyURL != nil {
|
|
||||||
connectHeader := make(http.Header)
|
|
||||||
if user := proxyURL.User; user != nil {
|
|
||||||
proxyUser := user.Username()
|
|
||||||
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
|
||||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
|
||||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectReq := &http.Request{
|
|
||||||
Method: "CONNECT",
|
|
||||||
URL: &url.URL{Opaque: hostPort},
|
|
||||||
Host: hostPort,
|
|
||||||
Header: connectHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
connectReq.Write(netConn)
|
|
||||||
|
|
||||||
// Read response.
|
|
||||||
// Okay to use and discard buffered reader here, because
|
|
||||||
// TLS server will not speak until spoken to.
|
|
||||||
br := bufio.NewReader(netConn)
|
|
||||||
resp, err := http.ReadResponse(br, connectReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
f := strings.SplitN(resp.Status, " ", 2)
|
|
||||||
return nil, nil, errors.New(f[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "https" {
|
if u.Scheme == "https" {
|
||||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||||
if cfg.ServerName == "" {
|
if cfg.ServerName == "" {
|
||||||
|
@ -329,22 +311,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
}
|
}
|
||||||
tlsConn := tls.Client(netConn, cfg)
|
tlsConn := tls.Client(netConn, cfg)
|
||||||
netConn = tlsConn
|
netConn = tlsConn
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
|
||||||
return nil, nil, err
|
var err error
|
||||||
|
if trace != nil {
|
||||||
|
err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
||||||
|
} else {
|
||||||
|
err = doHandshake(tlsConn, cfg)
|
||||||
}
|
}
|
||||||
if !cfg.InsecureSkipVerify {
|
|
||||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||||
|
|
||||||
if err := req.Write(netConn); err != nil {
|
if err := req.Write(netConn); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||||
|
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||||
|
trace.GotFirstResponseByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.ReadResponse(conn.br, req)
|
resp, err := http.ReadResponse(conn.br, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -390,3 +381,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||||
netConn = nil // to avoid close in defer.
|
netConn = nil // to avoid close in defer.
|
||||||
return conn, resp, nil
|
return conn, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !cfg.InsecureSkipVerify {
|
||||||
|
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ const (
|
||||||
// is UTF-8 encoded text.
|
// is UTF-8 encoded text.
|
||||||
PingMessage = 9
|
PingMessage = 9
|
||||||
|
|
||||||
// PongMessage denotes a ping control message. The optional message payload
|
// PongMessage denotes a pong control message. The optional message payload
|
||||||
// is UTF-8 encoded text.
|
// is UTF-8 encoded text.
|
||||||
PongMessage = 10
|
PongMessage = 10
|
||||||
)
|
)
|
||||||
|
@ -100,9 +100,8 @@ func (e *netError) Error() string { return e.msg }
|
||||||
func (e *netError) Temporary() bool { return e.temporary }
|
func (e *netError) Temporary() bool { return e.temporary }
|
||||||
func (e *netError) Timeout() bool { return e.timeout }
|
func (e *netError) Timeout() bool { return e.timeout }
|
||||||
|
|
||||||
// CloseError represents close frame.
|
// CloseError represents a close message.
|
||||||
type CloseError struct {
|
type CloseError struct {
|
||||||
|
|
||||||
// Code is defined in RFC 6455, section 11.7.
|
// Code is defined in RFC 6455, section 11.7.
|
||||||
Code int
|
Code int
|
||||||
|
|
||||||
|
@ -224,6 +223,20 @@ func isValidReceivedCloseCode(code int) bool {
|
||||||
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this
|
||||||
|
// interface. The type of the value stored in a pool is not specified.
|
||||||
|
type BufferPool interface {
|
||||||
|
// Get gets a value from the pool or returns nil if the pool is empty.
|
||||||
|
Get() interface{}
|
||||||
|
// Put adds a value to the pool.
|
||||||
|
Put(interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePoolData is the type added to the write buffer pool. This wrapper is
|
||||||
|
// used to prevent applications from peeking at and depending on the values
|
||||||
|
// added to the pool.
|
||||||
|
type writePoolData struct{ buf []byte }
|
||||||
|
|
||||||
// The Conn type represents a WebSocket connection.
|
// The Conn type represents a WebSocket connection.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
@ -233,6 +246,8 @@ type Conn struct {
|
||||||
// Write fields
|
// Write fields
|
||||||
mu chan bool // used as mutex to protect write to conn
|
mu chan bool // used as mutex to protect write to conn
|
||||||
writeBuf []byte // frame is constructed in this buffer.
|
writeBuf []byte // frame is constructed in this buffer.
|
||||||
|
writePool BufferPool
|
||||||
|
writeBufSize int
|
||||||
writeDeadline time.Time
|
writeDeadline time.Time
|
||||||
writer io.WriteCloser // the current writer returned to the application
|
writer io.WriteCloser // the current writer returned to the application
|
||||||
isWriting bool // for best-effort concurrent write detection
|
isWriting bool // for best-effort concurrent write detection
|
||||||
|
@ -264,64 +279,29 @@ type Conn struct {
|
||||||
newDecompressionReader func(io.Reader) io.ReadCloser
|
newDecompressionReader func(io.Reader) io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn {
|
||||||
return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeHook struct {
|
|
||||||
p []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wh *writeHook) Write(p []byte) (int, error) {
|
|
||||||
wh.p = p
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
|
|
||||||
mu := make(chan bool, 1)
|
|
||||||
mu <- true
|
|
||||||
|
|
||||||
var br *bufio.Reader
|
|
||||||
if readBufferSize == 0 && brw != nil && brw.Reader != nil {
|
|
||||||
// Reuse the supplied bufio.Reader if the buffer has a useful size.
|
|
||||||
// This code assumes that peek on a reader returns
|
|
||||||
// bufio.Reader.buf[:0].
|
|
||||||
brw.Reader.Reset(conn)
|
|
||||||
if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
|
|
||||||
br = brw.Reader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if br == nil {
|
if br == nil {
|
||||||
if readBufferSize == 0 {
|
if readBufferSize == 0 {
|
||||||
readBufferSize = defaultReadBufferSize
|
readBufferSize = defaultReadBufferSize
|
||||||
}
|
} else if readBufferSize < maxControlFramePayloadSize {
|
||||||
if readBufferSize < maxControlFramePayloadSize {
|
// must be large enough for control frame
|
||||||
readBufferSize = maxControlFramePayloadSize
|
readBufferSize = maxControlFramePayloadSize
|
||||||
}
|
}
|
||||||
br = bufio.NewReaderSize(conn, readBufferSize)
|
br = bufio.NewReaderSize(conn, readBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var writeBuf []byte
|
if writeBufferSize <= 0 {
|
||||||
if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
|
writeBufferSize = defaultWriteBufferSize
|
||||||
// Use the bufio.Writer's buffer if the buffer has a useful size. This
|
}
|
||||||
// code assumes that bufio.Writer.buf[:1] is passed to the
|
writeBufferSize += maxFrameHeaderSize
|
||||||
// bufio.Writer's underlying writer.
|
|
||||||
var wh writeHook
|
if writeBuf == nil && writeBufferPool == nil {
|
||||||
brw.Writer.Reset(&wh)
|
writeBuf = make([]byte, writeBufferSize)
|
||||||
brw.Writer.WriteByte(0)
|
|
||||||
brw.Flush()
|
|
||||||
if cap(wh.p) >= maxFrameHeaderSize+256 {
|
|
||||||
writeBuf = wh.p[:cap(wh.p)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if writeBuf == nil {
|
|
||||||
if writeBufferSize == 0 {
|
|
||||||
writeBufferSize = defaultWriteBufferSize
|
|
||||||
}
|
|
||||||
writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mu := make(chan bool, 1)
|
||||||
|
mu <- true
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
isServer: isServer,
|
isServer: isServer,
|
||||||
br: br,
|
br: br,
|
||||||
|
@ -329,6 +309,8 @@ func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize in
|
||||||
mu: mu,
|
mu: mu,
|
||||||
readFinal: true,
|
readFinal: true,
|
||||||
writeBuf: writeBuf,
|
writeBuf: writeBuf,
|
||||||
|
writePool: writeBufferPool,
|
||||||
|
writeBufSize: writeBufferSize,
|
||||||
enableWriteCompression: true,
|
enableWriteCompression: true,
|
||||||
compressionLevel: defaultCompressionLevel,
|
compressionLevel: defaultCompressionLevel,
|
||||||
}
|
}
|
||||||
|
@ -343,7 +325,8 @@ func (c *Conn) Subprotocol() string {
|
||||||
return c.subprotocol
|
return c.subprotocol
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the underlying network connection without sending or waiting for a close frame.
|
// Close closes the underlying network connection without sending or waiting
|
||||||
|
// for a close message.
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -370,7 +353,16 @@ func (c *Conn) writeFatal(err error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
c.br.Discard(len(p))
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||||
<-c.mu
|
<-c.mu
|
||||||
defer func() { c.mu <- true }()
|
defer func() { c.mu <- true }()
|
||||||
|
|
||||||
|
@ -382,15 +374,14 @@ func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.SetWriteDeadline(deadline)
|
c.conn.SetWriteDeadline(deadline)
|
||||||
for _, buf := range bufs {
|
if len(buf1) == 0 {
|
||||||
if len(buf) > 0 {
|
_, err = c.conn.Write(buf0)
|
||||||
_, err := c.conn.Write(buf)
|
} else {
|
||||||
if err != nil {
|
err = c.writeBufs(buf0, buf1)
|
||||||
return c.writeFatal(err)
|
}
|
||||||
}
|
if err != nil {
|
||||||
}
|
return c.writeFatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if frameType == CloseMessage {
|
if frameType == CloseMessage {
|
||||||
c.writeFatal(ErrCloseSent)
|
c.writeFatal(ErrCloseSent)
|
||||||
}
|
}
|
||||||
|
@ -476,7 +467,19 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||||
c.writeErrMu.Lock()
|
c.writeErrMu.Lock()
|
||||||
err := c.writeErr
|
err := c.writeErr
|
||||||
c.writeErrMu.Unlock()
|
c.writeErrMu.Unlock()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.writeBuf == nil {
|
||||||
|
wpd, ok := c.writePool.Get().(writePoolData)
|
||||||
|
if ok {
|
||||||
|
c.writeBuf = wpd.buf
|
||||||
|
} else {
|
||||||
|
c.writeBuf = make([]byte, c.writeBufSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextWriter returns a writer for the next message to send. The writer's Close
|
// NextWriter returns a writer for the next message to send. The writer's Close
|
||||||
|
@ -484,6 +487,9 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||||
//
|
//
|
||||||
// There can be at most one open writer on a connection. NextWriter closes the
|
// There can be at most one open writer on a connection. NextWriter closes the
|
||||||
// previous writer if the application has not already done so.
|
// previous writer if the application has not already done so.
|
||||||
|
//
|
||||||
|
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||||
|
// PongMessage) are supported.
|
||||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||||
if err := c.prepWrite(messageType); err != nil {
|
if err := c.prepWrite(messageType); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -599,6 +605,10 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||||
|
|
||||||
if final {
|
if final {
|
||||||
c.writer = nil
|
c.writer = nil
|
||||||
|
if c.writePool != nil {
|
||||||
|
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||||
|
c.writeBuf = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,7 +774,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
// Read methods
|
// Read methods
|
||||||
|
|
||||||
func (c *Conn) advanceFrame() (int, error) {
|
func (c *Conn) advanceFrame() (int, error) {
|
||||||
|
|
||||||
// 1. Skip remainder of previous frame.
|
// 1. Skip remainder of previous frame.
|
||||||
|
|
||||||
if c.readRemaining > 0 {
|
if c.readRemaining > 0 {
|
||||||
|
@ -1033,7 +1042,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||||
// message exceeds the limit, the connection sends a close frame to the peer
|
// message exceeds the limit, the connection sends a close message to the peer
|
||||||
// and returns ErrReadLimit to the application.
|
// and returns ErrReadLimit to the application.
|
||||||
func (c *Conn) SetReadLimit(limit int64) {
|
func (c *Conn) SetReadLimit(limit int64) {
|
||||||
c.readLimit = limit
|
c.readLimit = limit
|
||||||
|
@ -1046,24 +1055,22 @@ func (c *Conn) CloseHandler() func(code int, text string) error {
|
||||||
|
|
||||||
// SetCloseHandler sets the handler for close messages received from the peer.
|
// SetCloseHandler sets the handler for close messages received from the peer.
|
||||||
// The code argument to h is the received close code or CloseNoStatusReceived
|
// The code argument to h is the received close code or CloseNoStatusReceived
|
||||||
// if the close message is empty. The default close handler sends a close frame
|
// if the close message is empty. The default close handler sends a close
|
||||||
// back to the peer.
|
// message back to the peer.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process close messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// close messages as described in the section on Control Messages above.
|
||||||
//
|
//
|
||||||
// The connection read methods return a CloseError when a close frame is
|
// The connection read methods return a CloseError when a close message is
|
||||||
// received. Most applications should handle close messages as part of their
|
// received. Most applications should handle close messages as part of their
|
||||||
// normal error handling. Applications should only set a close handler when the
|
// normal error handling. Applications should only set a close handler when the
|
||||||
// application must perform some action before sending a close frame back to
|
// application must perform some action before sending a close message back to
|
||||||
// the peer.
|
// the peer.
|
||||||
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(code int, text string) error {
|
h = func(code int, text string) error {
|
||||||
message := []byte{}
|
message := FormatCloseMessage(code, "")
|
||||||
if code != CloseNoStatusReceived {
|
|
||||||
message = FormatCloseMessage(code, "")
|
|
||||||
}
|
|
||||||
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1077,11 +1084,12 @@ func (c *Conn) PingHandler() func(appData string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||||
// The appData argument to h is the PING frame application data. The default
|
// The appData argument to h is the PING message application data. The default
|
||||||
// ping handler sends a pong to the peer.
|
// ping handler sends a pong to the peer.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// ping messages as described in the section on Control Messages above.
|
||||||
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(message string) error {
|
h = func(message string) error {
|
||||||
|
@ -1103,11 +1111,12 @@ func (c *Conn) PongHandler() func(appData string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||||
// The appData argument to h is the PONG frame application data. The default
|
// The appData argument to h is the PONG message application data. The default
|
||||||
// pong handler does nothing.
|
// pong handler does nothing.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping messages as
|
// The handler function is called from the NextReader, ReadMessage and message
|
||||||
// described in the section on Control Frames above.
|
// reader Read methods. The application must read the connection to process
|
||||||
|
// pong messages as described in the section on Control Messages above.
|
||||||
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(string) error { return nil }
|
h = func(string) error { return nil }
|
||||||
|
@ -1141,7 +1150,14 @@ func (c *Conn) SetCompressionLevel(level int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
||||||
|
// An empty message is returned for code CloseNoStatusReceived.
|
||||||
func FormatCloseMessage(closeCode int, text string) []byte {
|
func FormatCloseMessage(closeCode int, text string) []byte {
|
||||||
|
if closeCode == CloseNoStatusReceived {
|
||||||
|
// Return empty message because it's illegal to send
|
||||||
|
// CloseNoStatusReceived. Return non-nil value in case application
|
||||||
|
// checks for nil.
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
buf := make([]byte, 2+len(text))
|
buf := make([]byte, 2+len(text))
|
||||||
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
||||||
copy(buf[2:], text)
|
copy(buf[2:], text)
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright 2016 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.
|
|
||||||
|
|
||||||
// +build !go1.5
|
|
||||||
|
|
||||||
package websocket
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
func (c *Conn) read(n int) ([]byte, error) {
|
|
||||||
p, err := c.br.Peek(n)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = errUnexpectedEOF
|
|
||||||
}
|
|
||||||
if len(p) > 0 {
|
|
||||||
// advance over the bytes just read
|
|
||||||
io.ReadFull(c.br, p)
|
|
||||||
}
|
|
||||||
return p, err
|
|
||||||
}
|
|
|
@ -2,17 +2,14 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.5
|
// +build go1.8
|
||||||
|
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import "io"
|
import "net"
|
||||||
|
|
||||||
func (c *Conn) read(n int) ([]byte, error) {
|
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||||
p, err := c.br.Peek(n)
|
b := net.Buffers(bufs)
|
||||||
if err == io.EOF {
|
_, err := b.WriteTo(c.conn)
|
||||||
err = errUnexpectedEOF
|
return err
|
||||||
}
|
|
||||||
c.br.Discard(len(p))
|
|
||||||
return p, err
|
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||||
|
for _, buf := range bufs {
|
||||||
|
if len(buf) > 0 {
|
||||||
|
if _, err := c.conn.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,9 +6,8 @@
|
||||||
//
|
//
|
||||||
// Overview
|
// Overview
|
||||||
//
|
//
|
||||||
// The Conn type represents a WebSocket connection. A server application uses
|
// The Conn type represents a WebSocket connection. A server application calls
|
||||||
// the Upgrade function from an Upgrader object with a HTTP request handler
|
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||||
// to get a pointer to a Conn:
|
|
||||||
//
|
//
|
||||||
// var upgrader = websocket.Upgrader{
|
// var upgrader = websocket.Upgrader{
|
||||||
// ReadBufferSize: 1024,
|
// ReadBufferSize: 1024,
|
||||||
|
@ -31,10 +30,12 @@
|
||||||
// for {
|
// for {
|
||||||
// messageType, p, err := conn.ReadMessage()
|
// messageType, p, err := conn.ReadMessage()
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
// if err = conn.WriteMessage(messageType, p); err != nil {
|
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||||
// return err
|
// log.Println(err)
|
||||||
|
// return
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -85,20 +86,26 @@
|
||||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||||
// methods to send a control message to the peer.
|
// methods to send a control message to the peer.
|
||||||
//
|
//
|
||||||
// Connections handle received close messages by sending a close message to the
|
// Connections handle received close messages by calling the handler function
|
||||||
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||||
// message Read method.
|
// NextReader, ReadMessage or the message Read method. The default close
|
||||||
|
// handler sends a close message to the peer.
|
||||||
//
|
//
|
||||||
// Connections handle received ping and pong messages by invoking callback
|
// Connections handle received ping messages by calling the handler function
|
||||||
// functions set with SetPingHandler and SetPongHandler methods. The callback
|
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||||
// functions are called from the NextReader, ReadMessage and the message Read
|
// message to the peer.
|
||||||
// methods.
|
|
||||||
//
|
//
|
||||||
// The default ping handler sends a pong to the peer. The application's reading
|
// Connections handle received pong messages by calling the handler function
|
||||||
// goroutine can block for a short time while the handler writes the pong data
|
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||||
// to the connection.
|
// If an application sends ping messages, then the application should set a
|
||||||
|
// pong handler to receive the corresponding pong.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping, pong and close
|
// The control message handler functions are called from the NextReader,
|
||||||
|
// ReadMessage and message reader Read methods. The default close and ping
|
||||||
|
// handlers can block these methods for a short time when the handler writes to
|
||||||
|
// the connection.
|
||||||
|
//
|
||||||
|
// The application must read the connection to process close, ping and pong
|
||||||
// messages sent from the peer. If the application is not otherwise interested
|
// messages sent from the peer. If the application is not otherwise interested
|
||||||
// in messages from the peer, then the application should start a goroutine to
|
// in messages from the peer, then the application should start a goroutine to
|
||||||
// read and discard messages from the peer. A simple example is:
|
// read and discard messages from the peer. A simple example is:
|
||||||
|
@ -137,19 +144,12 @@
|
||||||
// method fails the WebSocket handshake with HTTP status 403.
|
// method fails the WebSocket handshake with HTTP status 403.
|
||||||
//
|
//
|
||||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||||
// the handshake if the Origin request header is present and not equal to the
|
// the handshake if the Origin request header is present and the Origin host is
|
||||||
// Host request header.
|
// not equal to the Host request header.
|
||||||
//
|
//
|
||||||
// An application can allow connections from any origin by specifying a
|
// The deprecated package-level Upgrade function does not perform origin
|
||||||
// function that always returns true:
|
// checking. The application is responsible for checking the Origin header
|
||||||
//
|
// before calling the Upgrade function.
|
||||||
// var upgrader = websocket.Upgrader{
|
|
||||||
// CheckOrigin: func(r *http.Request) bool { return true },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
|
||||||
// application's responsibility to check the Origin header before calling
|
|
||||||
// Upgrade.
|
|
||||||
//
|
//
|
||||||
// Compression EXPERIMENTAL
|
// Compression EXPERIMENTAL
|
||||||
//
|
//
|
||||||
|
|
|
@ -9,12 +9,14 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteJSON is deprecated, use c.WriteJSON instead.
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.WriteJSON instead.
|
||||||
func WriteJSON(c *Conn, v interface{}) error {
|
func WriteJSON(c *Conn, v interface{}) error {
|
||||||
return c.WriteJSON(v)
|
return c.WriteJSON(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteJSON writes the JSON encoding of v to the connection.
|
// WriteJSON writes the JSON encoding of v as a message.
|
||||||
//
|
//
|
||||||
// See the documentation for encoding/json Marshal for details about the
|
// See the documentation for encoding/json Marshal for details about the
|
||||||
// conversion of Go values to JSON.
|
// conversion of Go values to JSON.
|
||||||
|
@ -31,7 +33,10 @@ func (c *Conn) WriteJSON(v interface{}) error {
|
||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadJSON is deprecated, use c.ReadJSON instead.
|
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||||
|
// it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Deprecated: Use c.ReadJSON instead.
|
||||||
func ReadJSON(c *Conn, v interface{}) error {
|
func ReadJSON(c *Conn, v interface{}) error {
|
||||||
return c.ReadJSON(v)
|
return c.ReadJSON(v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import "unsafe"
|
||||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
|
||||||
// Mask one byte at a time for small buffers.
|
// Mask one byte at a time for small buffers.
|
||||||
if len(b) < 2*wordSize {
|
if len(b) < 2*wordSize {
|
||||||
for i := range b {
|
for i := range b {
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
type PreparedMessage struct {
|
type PreparedMessage struct {
|
||||||
messageType int
|
messageType int
|
||||||
data []byte
|
data []byte
|
||||||
err error
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
frames map[prepareKey]*preparedFrame
|
frames map[prepareKey]*preparedFrame
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netDialerFunc func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return fn(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProxyDialer struct {
|
||||||
|
proxyURL *url.URL
|
||||||
|
fowardDial func(network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||||
|
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||||
|
conn, err := hpd.fowardDial(network, hostPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectHeader := make(http.Header)
|
||||||
|
if user := hpd.proxyURL.User; user != nil {
|
||||||
|
proxyUser := user.Username()
|
||||||
|
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||||
|
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||||
|
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{Opaque: addr},
|
||||||
|
Host: addr,
|
||||||
|
Header: connectHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := connectReq.Write(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response. It's OK to use and discard buffered reader here becaue
|
||||||
|
// the remote server does not speak until spoken to.
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
resp, err := http.ReadResponse(br, connectReq)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
conn.Close()
|
||||||
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
|
return nil, errors.New(f[1])
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ package websocket
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -33,10 +33,23 @@ type Upgrader struct {
|
||||||
// or received.
|
// or received.
|
||||||
ReadBufferSize, WriteBufferSize int
|
ReadBufferSize, WriteBufferSize int
|
||||||
|
|
||||||
|
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||||
|
// is not set, then write buffers are allocated to the connection for the
|
||||||
|
// lifetime of the connection.
|
||||||
|
//
|
||||||
|
// A pool is most useful when the application has a modest volume of writes
|
||||||
|
// across a large number of connections.
|
||||||
|
//
|
||||||
|
// Applications should use a single pool for each unique value of
|
||||||
|
// WriteBufferSize.
|
||||||
|
WriteBufferPool BufferPool
|
||||||
|
|
||||||
// Subprotocols specifies the server's supported protocols in order of
|
// Subprotocols specifies the server's supported protocols in order of
|
||||||
// preference. If this field is set, then the Upgrade method negotiates a
|
// preference. If this field is not nil, then the Upgrade method negotiates a
|
||||||
// subprotocol by selecting the first match in this list with a protocol
|
// subprotocol by selecting the first match in this list with a protocol
|
||||||
// requested by the client.
|
// requested by the client. If there's no match, then no protocol is
|
||||||
|
// negotiated (the Sec-Websocket-Protocol header is not included in the
|
||||||
|
// handshake response).
|
||||||
Subprotocols []string
|
Subprotocols []string
|
||||||
|
|
||||||
// Error specifies the function for generating HTTP error responses. If Error
|
// Error specifies the function for generating HTTP error responses. If Error
|
||||||
|
@ -44,8 +57,12 @@ type Upgrader struct {
|
||||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||||
|
|
||||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
// CheckOrigin is nil, then a safe default is used: return false if the
|
||||||
// must match the host of the request.
|
// Origin request header is present and the origin host is not equal to
|
||||||
|
// request Host header.
|
||||||
|
//
|
||||||
|
// A CheckOrigin function should carefully validate the request origin to
|
||||||
|
// prevent cross-site request forgery.
|
||||||
CheckOrigin func(r *http.Request) bool
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
|
||||||
// EnableCompression specify if the server should attempt to negotiate per
|
// EnableCompression specify if the server should attempt to negotiate per
|
||||||
|
@ -76,7 +93,7 @@ func checkSameOrigin(r *http.Request) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.Host == r.Host
|
return equalASCIIFold(u.Host, r.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||||
|
@ -99,42 +116,44 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
|
||||||
//
|
//
|
||||||
// The responseHeader is included in the response to the client's upgrade
|
// The responseHeader is included in the response to the client's upgrade
|
||||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
// application negotiated subprotocol (Sec-WebSocket-Protocol).
|
||||||
//
|
//
|
||||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||||
// response.
|
// response.
|
||||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||||
if r.Method != "GET" {
|
const badHandshake = "websocket: the client is not using the websocket protocol: "
|
||||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
|
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
checkOrigin := u.CheckOrigin
|
checkOrigin := u.CheckOrigin
|
||||||
if checkOrigin == nil {
|
if checkOrigin == nil {
|
||||||
checkOrigin = checkSameOrigin
|
checkOrigin = checkSameOrigin
|
||||||
}
|
}
|
||||||
if !checkOrigin(r) {
|
if !checkOrigin(r) {
|
||||||
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||||
if challengeKey == "" {
|
if challengeKey == "" {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||||
|
@ -151,17 +170,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
netConn net.Conn
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
h, ok := w.(http.Hijacker)
|
h, ok := w.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||||
}
|
}
|
||||||
var brw *bufio.ReadWriter
|
var brw *bufio.ReadWriter
|
||||||
netConn, brw, err = h.Hijack()
|
netConn, brw, err := h.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -171,7 +185,21 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
var br *bufio.Reader
|
||||||
|
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {
|
||||||
|
// Reuse hijacked buffered reader as connection reader.
|
||||||
|
br = brw.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufioWriterBuffer(netConn, brw.Writer)
|
||||||
|
|
||||||
|
var writeBuf []byte
|
||||||
|
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
|
||||||
|
// Reuse hijacked write buffer as connection buffer.
|
||||||
|
writeBuf = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)
|
||||||
c.subprotocol = subprotocol
|
c.subprotocol = subprotocol
|
||||||
|
|
||||||
if compress {
|
if compress {
|
||||||
|
@ -179,17 +207,23 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||||
c.newDecompressionReader = decompressNoContextTakeover
|
c.newDecompressionReader = decompressNoContextTakeover
|
||||||
}
|
}
|
||||||
|
|
||||||
p := c.writeBuf[:0]
|
// Use larger of hijacked buffer and connection write buffer for header.
|
||||||
|
p := buf
|
||||||
|
if len(c.writeBuf) > len(p) {
|
||||||
|
p = c.writeBuf
|
||||||
|
}
|
||||||
|
p = p[:0]
|
||||||
|
|
||||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||||
p = append(p, computeAcceptKey(challengeKey)...)
|
p = append(p, computeAcceptKey(challengeKey)...)
|
||||||
p = append(p, "\r\n"...)
|
p = append(p, "\r\n"...)
|
||||||
if c.subprotocol != "" {
|
if c.subprotocol != "" {
|
||||||
p = append(p, "Sec-Websocket-Protocol: "...)
|
p = append(p, "Sec-WebSocket-Protocol: "...)
|
||||||
p = append(p, c.subprotocol...)
|
p = append(p, c.subprotocol...)
|
||||||
p = append(p, "\r\n"...)
|
p = append(p, "\r\n"...)
|
||||||
}
|
}
|
||||||
if compress {
|
if compress {
|
||||||
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||||
}
|
}
|
||||||
for k, vs := range responseHeader {
|
for k, vs := range responseHeader {
|
||||||
if k == "Sec-Websocket-Protocol" {
|
if k == "Sec-Websocket-Protocol" {
|
||||||
|
@ -230,13 +264,14 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||||
|
|
||||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||||
//
|
//
|
||||||
// This function is deprecated, use websocket.Upgrader instead.
|
// Deprecated: Use websocket.Upgrader instead.
|
||||||
//
|
//
|
||||||
// The application is responsible for checking the request origin before
|
// Upgrade does not perform origin checking. The application is responsible for
|
||||||
// calling Upgrade. An example implementation of the same origin policy is:
|
// checking the Origin header before calling Upgrade. An example implementation
|
||||||
|
// of the same origin policy check is:
|
||||||
//
|
//
|
||||||
// if req.Header.Get("Origin") != "http://"+req.Host {
|
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||||
// http.Error(w, "Origin not allowed", 403)
|
// http.Error(w, "Origin not allowed", http.StatusForbidden)
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -289,3 +324,40 @@ func IsWebSocketUpgrade(r *http.Request) bool {
|
||||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||||
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bufioReaderSize size returns the size of a bufio.Reader.
|
||||||
|
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
|
||||||
|
// This code assumes that peek on a reset reader returns
|
||||||
|
// bufio.Reader.buf[:0].
|
||||||
|
// TODO: Use bufio.Reader.Size() after Go 1.10
|
||||||
|
br.Reset(originalReader)
|
||||||
|
if p, err := br.Peek(0); err == nil {
|
||||||
|
return cap(p)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHook is an io.Writer that records the last slice passed to it vio
|
||||||
|
// io.Writer.Write.
|
||||||
|
type writeHook struct {
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||||
|
wh.p = p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
|
||||||
|
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
|
||||||
|
// This code assumes that bufio.Writer.buf[:1] is passed to the
|
||||||
|
// bufio.Writer's underlying writer.
|
||||||
|
var wh writeHook
|
||||||
|
bw.Reset(&wh)
|
||||||
|
bw.WriteByte(0)
|
||||||
|
bw.Flush()
|
||||||
|
|
||||||
|
bw.Reset(originalWriter)
|
||||||
|
|
||||||
|
return wh.p[:cap(wh.p)]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http/httptrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
if trace.TLSHandshakeStart != nil {
|
||||||
|
trace.TLSHandshakeStart()
|
||||||
|
}
|
||||||
|
err := doHandshake(tlsConn, cfg)
|
||||||
|
if trace.TLSHandshakeDone != nil {
|
||||||
|
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http/httptrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||||
|
return doHandshake(tlsConn, cfg)
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||||
|
@ -111,14 +112,14 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||||
case escape:
|
case escape:
|
||||||
escape = false
|
escape = false
|
||||||
p[j] = b
|
p[j] = b
|
||||||
j += 1
|
j++
|
||||||
case b == '\\':
|
case b == '\\':
|
||||||
escape = true
|
escape = true
|
||||||
case b == '"':
|
case b == '"':
|
||||||
return string(p[:j]), s[i+1:]
|
return string(p[:j]), s[i+1:]
|
||||||
default:
|
default:
|
||||||
p[j] = b
|
p[j] = b
|
||||||
j += 1
|
j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", ""
|
return "", ""
|
||||||
|
@ -127,8 +128,31 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
||||||
|
func equalASCIIFold(s, t string) bool {
|
||||||
|
for s != "" && t != "" {
|
||||||
|
sr, size := utf8.DecodeRuneInString(s)
|
||||||
|
s = s[size:]
|
||||||
|
tr, size := utf8.DecodeRuneInString(t)
|
||||||
|
t = t[size:]
|
||||||
|
if sr == tr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= sr && sr <= 'Z' {
|
||||||
|
sr = sr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if 'A' <= tr && tr <= 'Z' {
|
||||||
|
tr = tr + 'a' - 'A'
|
||||||
|
}
|
||||||
|
if sr != tr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s == t
|
||||||
|
}
|
||||||
|
|
||||||
// tokenListContainsValue returns true if the 1#token header with the given
|
// tokenListContainsValue returns true if the 1#token header with the given
|
||||||
// name contains token.
|
// name contains a token equal to value with ASCII case folding.
|
||||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||||
headers:
|
headers:
|
||||||
for _, s := range header[name] {
|
for _, s := range header[name] {
|
||||||
|
@ -142,7 +166,7 @@ headers:
|
||||||
if s != "" && s[0] != ',' {
|
if s != "" && s[0] != ',' {
|
||||||
continue headers
|
continue headers
|
||||||
}
|
}
|
||||||
if strings.EqualFold(t, value) {
|
if equalASCIIFold(t, value) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
@ -154,9 +178,8 @@ headers:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseExtensiosn parses WebSocket extensions from a header.
|
// parseExtensions parses WebSocket extensions from a header.
|
||||||
func parseExtensions(header http.Header) []map[string]string {
|
func parseExtensions(header http.Header) []map[string]string {
|
||||||
|
|
||||||
// From RFC 6455:
|
// From RFC 6455:
|
||||||
//
|
//
|
||||||
// Sec-WebSocket-Extensions = extension-list
|
// Sec-WebSocket-Extensions = extension-list
|
||||||
|
|
|
@ -0,0 +1,473 @@
|
||||||
|
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
||||||
|
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
|
||||||
|
|
||||||
|
// Package proxy provides support for a variety of protocols to proxy network
|
||||||
|
// data.
|
||||||
|
//
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxy_direct struct{}
|
||||||
|
|
||||||
|
// Direct is a direct proxy: one that makes network connections directly.
|
||||||
|
var proxy_Direct = proxy_direct{}
|
||||||
|
|
||||||
|
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
|
// requested matches one of a number of exceptions.
|
||||||
|
type proxy_PerHost struct {
|
||||||
|
def, bypass proxy_Dialer
|
||||||
|
|
||||||
|
bypassNetworks []*net.IPNet
|
||||||
|
bypassIPs []net.IP
|
||||||
|
bypassZones []string
|
||||||
|
bypassHosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||||
|
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||||
|
// the configured rules.
|
||||||
|
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
|
||||||
|
return &proxy_PerHost{
|
||||||
|
def: defaultDialer,
|
||||||
|
bypass: bypass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network through either
|
||||||
|
// defaultDialer or bypass.
|
||||||
|
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.dialerForRequest(host).Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
for _, net := range p.bypassNetworks {
|
||||||
|
if net.Contains(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassIP := range p.bypassIPs {
|
||||||
|
if bypassIP.Equal(ip) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range p.bypassZones {
|
||||||
|
if strings.HasSuffix(host, zone) {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
if host == zone[1:] {
|
||||||
|
// For a zone ".example.com", we match "example.com"
|
||||||
|
// too.
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bypassHost := range p.bypassHosts {
|
||||||
|
if bypassHost == host {
|
||||||
|
return p.bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.def
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromString parses a string that contains comma-separated values
|
||||||
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
|
// ignored.
|
||||||
|
func (p *proxy_PerHost) AddFromString(s string) {
|
||||||
|
hosts := strings.Split(s, ",")
|
||||||
|
for _, host := range hosts {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if len(host) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "/") {
|
||||||
|
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||||
|
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||||
|
p.AddNetwork(net)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
p.AddIP(ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
p.AddZone(host[1:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.AddHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match an IP.
|
||||||
|
func (p *proxy_PerHost) AddIP(ip net.IP) {
|
||||||
|
p.bypassIPs = append(p.bypassIPs, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||||
|
// this will only take effect if a literal IP address is dialed. A connection
|
||||||
|
// to a named host will never match.
|
||||||
|
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
|
||||||
|
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||||
|
// "example.com" matches "example.com" and all of its subdomains.
|
||||||
|
func (p *proxy_PerHost) AddZone(zone string) {
|
||||||
|
if strings.HasSuffix(zone, ".") {
|
||||||
|
zone = zone[:len(zone)-1]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(zone, ".") {
|
||||||
|
zone = "." + zone
|
||||||
|
}
|
||||||
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
|
func (p *proxy_PerHost) AddHost(host string) {
|
||||||
|
if strings.HasSuffix(host, ".") {
|
||||||
|
host = host[:len(host)-1]
|
||||||
|
}
|
||||||
|
p.bypassHosts = append(p.bypassHosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dialer is a means to establish a connection.
|
||||||
|
type proxy_Dialer interface {
|
||||||
|
// Dial connects to the given address via the proxy.
|
||||||
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth contains authentication parameters that specific Dialers may require.
|
||||||
|
type proxy_Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||||
|
// the environment.
|
||||||
|
func proxy_FromEnvironment() proxy_Dialer {
|
||||||
|
allProxy := proxy_allProxyEnv.Get()
|
||||||
|
if len(allProxy) == 0 {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(allProxy)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
|
||||||
|
if err != nil {
|
||||||
|
return proxy_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
noProxy := proxy_noProxyEnv.Get()
|
||||||
|
if len(noProxy) == 0 {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
perHost := proxy_NewPerHost(proxy, proxy_Direct)
|
||||||
|
perHost.AddFromString(noProxy)
|
||||||
|
return perHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||||
|
// from a URL with such a scheme.
|
||||||
|
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
|
||||||
|
|
||||||
|
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||||
|
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||||
|
// by FromURL.
|
||||||
|
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
|
||||||
|
if proxy_proxySchemes == nil {
|
||||||
|
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
|
||||||
|
}
|
||||||
|
proxy_proxySchemes[scheme] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromURL returns a Dialer given a URL specification and an underlying
|
||||||
|
// Dialer for it to make network requests.
|
||||||
|
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
var auth *proxy_Auth
|
||||||
|
if u.User != nil {
|
||||||
|
auth = new(proxy_Auth)
|
||||||
|
auth.User = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
auth.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "socks5":
|
||||||
|
return proxy_SOCKS5("tcp", u.Host, auth, forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||||
|
// was registered by another package.
|
||||||
|
if proxy_proxySchemes != nil {
|
||||||
|
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
|
||||||
|
return f(u, forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
proxy_allProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
proxy_noProxyEnv = &proxy_envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type proxy_envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *proxy_envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
|
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||||
|
s := &proxy_socks5{
|
||||||
|
network: network,
|
||||||
|
addr: addr,
|
||||||
|
forward: forward,
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
s.user = auth.User
|
||||||
|
s.password = auth.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy_socks5 struct {
|
||||||
|
user, password string
|
||||||
|
network, addr string
|
||||||
|
forward proxy_Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy_socks5Version = 5
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5AuthNone = 0
|
||||||
|
proxy_socks5AuthPassword = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const proxy_socks5Connect = 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxy_socks5IP4 = 1
|
||||||
|
proxy_socks5Domain = 3
|
||||||
|
proxy_socks5IP6 = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxy_socks5Errors = []string{
|
||||||
|
"",
|
||||||
|
"general failure",
|
||||||
|
"connection forbidden",
|
||||||
|
"network unreachable",
|
||||||
|
"host unreachable",
|
||||||
|
"connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"command not supported",
|
||||||
|
"address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
|
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.forward.Dial(s.network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.connect(conn, addr); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
|
||||||
|
host, portStr, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, proxy_socks5Version)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
|
if buf[1] == proxy_socks5AuthPassword {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, proxy_socks5IP4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, proxy_socks5IP6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, proxy_socks5Domain)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(proxy_socks5Errors) {
|
||||||
|
failure = proxy_socks5Errors[buf[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case proxy_socks5IP4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case proxy_socks5IP6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case proxy_socks5Domain:
|
||||||
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also need to discard the port number
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,17 +2,15 @@ language: go
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- "1.12.x"
|
||||||
- 1.11.x
|
- "1.13.x"
|
||||||
- 1.12.x
|
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
before_install:
|
env:
|
||||||
# don't use the miekg/dns when testing forks
|
- GO111MODULE=on
|
||||||
- mkdir -p $GOPATH/src/github.com/miekg
|
|
||||||
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- go generate ./... && test `git ls-files --modified | wc -l` = 0
|
||||||
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
* @miekg @tmthrgd
|
|
@ -1,57 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:6914c49eed986dfb8dffb33516fa129c49929d4d873f41e073c83c11c372b870"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = [
|
|
||||||
"ed25519",
|
|
||||||
"ed25519/internal/edwards25519",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:08e41d63f8dac84d83797368b56cf0b339e42d0224e5e56668963c28aec95685"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"bpf",
|
|
||||||
"context",
|
|
||||||
"internal/iana",
|
|
||||||
"internal/socket",
|
|
||||||
"ipv4",
|
|
||||||
"ipv6",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "4dfa2610cdf3b287375bbba5b8f2a14d3b01d8de"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:b2ea75de0ccb2db2ac79356407f8a4cd8f798fe15d41b381c00abf3ae8e55ed1"
|
|
||||||
name = "golang.org/x/sync"
|
|
||||||
packages = ["errgroup"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:149a432fabebb8221a80f77731b1cd63597197ded4f14af606ebe3a0959004ec"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"golang.org/x/crypto/ed25519",
|
|
||||||
"golang.org/x/net/ipv4",
|
|
||||||
"golang.org/x/net/ipv6",
|
|
||||||
"golang.org/x/sync/errgroup",
|
|
||||||
"golang.org/x/sys/unix",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sync"
|
|
|
@ -1,7 +1,3 @@
|
||||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
|
||||||
|
|
||||||
As this is fork of the official Go code the same license applies:
|
|
||||||
|
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -30,3 +26,5 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
As this is fork of the official Go code the same license applies.
|
||||||
|
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||||
|
|
|
@ -69,6 +69,7 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
||||||
* https://github.com/semihalev/sdns
|
* https://github.com/semihalev/sdns
|
||||||
* https://render.com
|
* https://render.com
|
||||||
* https://github.com/peterzen/goresolver
|
* https://github.com/peterzen/goresolver
|
||||||
|
* https://github.com/folbricht/routedns
|
||||||
|
|
||||||
Send pull request if you want to be listed here.
|
Send pull request if you want to be listed here.
|
||||||
|
|
||||||
|
@ -93,8 +94,8 @@ DNS Authors 2012-
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
Building is done with the `go` tool. If you have setup your GOPATH correctly, the following should
|
This library uses Go modules and uses semantic versioning. Building is done with the `go` tool, so
|
||||||
work:
|
the following should work:
|
||||||
|
|
||||||
go get github.com/miekg/dns
|
go get github.com/miekg/dns
|
||||||
go build github.com/miekg/dns
|
go build github.com/miekg/dns
|
||||||
|
@ -126,6 +127,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
||||||
* 2915 - NAPTR record
|
* 2915 - NAPTR record
|
||||||
* 2929 - DNS IANA Considerations
|
* 2929 - DNS IANA Considerations
|
||||||
* 3110 - RSASHA1 DNS keys
|
* 3110 - RSASHA1 DNS keys
|
||||||
|
* 3123 - APL record
|
||||||
* 3225 - DO bit (DNSSEC OK)
|
* 3225 - DO bit (DNSSEC OK)
|
||||||
* 340{1,2,3} - NAPTR record
|
* 340{1,2,3} - NAPTR record
|
||||||
* 3445 - Limiting the scope of (DNS)KEY
|
* 3445 - Limiting the scope of (DNS)KEY
|
||||||
|
@ -152,6 +154,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
|
||||||
* 6844 - CAA record
|
* 6844 - CAA record
|
||||||
* 6891 - EDNS0 update
|
* 6891 - EDNS0 update
|
||||||
* 6895 - DNS IANA considerations
|
* 6895 - DNS IANA considerations
|
||||||
|
* 6944 - DNSSEC DNSKEY Algorithm Status
|
||||||
* 6975 - Algorithm Understanding in DNSSEC
|
* 6975 - Algorithm Understanding in DNSSEC
|
||||||
* 7043 - EUI48/EUI64 records
|
* 7043 - EUI48/EUI64 records
|
||||||
* 7314 - DNS (EDNS) EXPIRE Option
|
* 7314 - DNS (EDNS) EXPIRE Option
|
||||||
|
|
|
@ -6,22 +6,30 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
||||||
|
|
||||||
// DefaultMsgAcceptFunc checks the request and will reject if:
|
// DefaultMsgAcceptFunc checks the request and will reject if:
|
||||||
//
|
//
|
||||||
// * isn't a request (don't respond in that case).
|
// * isn't a request (don't respond in that case)
|
||||||
|
//
|
||||||
// * opcode isn't OpcodeQuery or OpcodeNotify
|
// * opcode isn't OpcodeQuery or OpcodeNotify
|
||||||
|
//
|
||||||
// * Zero bit isn't zero
|
// * Zero bit isn't zero
|
||||||
|
//
|
||||||
// * has more than 1 question in the question section
|
// * has more than 1 question in the question section
|
||||||
|
//
|
||||||
// * has more than 1 RR in the Answer section
|
// * has more than 1 RR in the Answer section
|
||||||
|
//
|
||||||
// * has more than 0 RRs in the Authority section
|
// * has more than 0 RRs in the Authority section
|
||||||
|
//
|
||||||
// * has more than 2 RRs in the Additional section
|
// * has more than 2 RRs in the Additional section
|
||||||
|
//
|
||||||
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
|
||||||
|
|
||||||
// MsgAcceptAction represents the action to be taken.
|
// MsgAcceptAction represents the action to be taken.
|
||||||
type MsgAcceptAction int
|
type MsgAcceptAction int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MsgAccept MsgAcceptAction = iota // Accept the message
|
MsgAccept MsgAcceptAction = iota // Accept the message
|
||||||
MsgReject // Reject the message with a RcodeFormatError
|
MsgReject // Reject the message with a RcodeFormatError
|
||||||
MsgIgnore // Ignore the error and send nothing back.
|
MsgIgnore // Ignore the error and send nothing back.
|
||||||
|
MsgRejectNotImplemented // Reject the message with a RcodeNotImplemented
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultMsgAcceptFunc(dh Header) MsgAcceptAction {
|
func defaultMsgAcceptFunc(dh Header) MsgAcceptAction {
|
||||||
|
@ -32,12 +40,9 @@ func defaultMsgAcceptFunc(dh Header) MsgAcceptAction {
|
||||||
// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
|
// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
|
||||||
opcode := int(dh.Bits>>11) & 0xF
|
opcode := int(dh.Bits>>11) & 0xF
|
||||||
if opcode != OpcodeQuery && opcode != OpcodeNotify {
|
if opcode != OpcodeQuery && opcode != OpcodeNotify {
|
||||||
return MsgReject
|
return MsgRejectNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
if isZero := dh.Bits&_Z != 0; isZero {
|
|
||||||
return MsgReject
|
|
||||||
}
|
|
||||||
if dh.Qdcount != 1 {
|
if dh.Qdcount != 1 {
|
||||||
return MsgReject
|
return MsgReject
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -128,20 +129,15 @@ func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, er
|
||||||
return c.exchange(m, address)
|
return c.exchange(m, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := "nop"
|
q := m.Question[0]
|
||||||
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
|
||||||
t = t1
|
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
|
||||||
}
|
|
||||||
cl := "nop"
|
|
||||||
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
|
||||||
cl = cl1
|
|
||||||
}
|
|
||||||
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
|
||||||
return c.exchange(m, address)
|
return c.exchange(m, address)
|
||||||
})
|
})
|
||||||
if r != nil && shared {
|
if r != nil && shared {
|
||||||
r = r.Copy()
|
r = r.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, rtt, err
|
return r, rtt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,8 +215,15 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
||||||
n int
|
n int
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
switch co.Conn.(type) {
|
|
||||||
case *net.TCPConn, *tls.Conn:
|
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||||
|
if co.UDPSize > MinMsgSize {
|
||||||
|
p = make([]byte, co.UDPSize)
|
||||||
|
} else {
|
||||||
|
p = make([]byte, MinMsgSize)
|
||||||
|
}
|
||||||
|
n, err = co.Read(p)
|
||||||
|
} else {
|
||||||
var length uint16
|
var length uint16
|
||||||
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -228,13 +231,6 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
||||||
|
|
||||||
p = make([]byte, length)
|
p = make([]byte, length)
|
||||||
n, err = io.ReadFull(co.Conn, p)
|
n, err = io.ReadFull(co.Conn, p)
|
||||||
default:
|
|
||||||
if co.UDPSize > MinMsgSize {
|
|
||||||
p = make([]byte, co.UDPSize)
|
|
||||||
} else {
|
|
||||||
p = make([]byte, MinMsgSize)
|
|
||||||
}
|
|
||||||
n, err = co.Read(p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -260,21 +256,20 @@ func (co *Conn) Read(p []byte) (n int, err error) {
|
||||||
return 0, ErrConnEmpty
|
return 0, ErrConnEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
switch co.Conn.(type) {
|
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||||
case *net.TCPConn, *tls.Conn:
|
// UDP connection
|
||||||
var length uint16
|
return co.Conn.Read(p)
|
||||||
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if int(length) > len(p) {
|
|
||||||
return 0, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
return io.ReadFull(co.Conn, p[:length])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP connection
|
var length uint16
|
||||||
return co.Conn.Read(p)
|
if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if int(length) > len(p) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadFull(co.Conn, p[:length])
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg sends a message through the connection co.
|
// WriteMsg sends a message through the connection co.
|
||||||
|
@ -301,21 +296,20 @@ func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements the net.Conn Write method.
|
// Write implements the net.Conn Write method.
|
||||||
func (co *Conn) Write(p []byte) (n int, err error) {
|
func (co *Conn) Write(p []byte) (int, error) {
|
||||||
switch co.Conn.(type) {
|
if len(p) > MaxMsgSize {
|
||||||
case *net.TCPConn, *tls.Conn:
|
return 0, &Error{err: "message too large"}
|
||||||
if len(p) > MaxMsgSize {
|
|
||||||
return 0, &Error{err: "message too large"}
|
|
||||||
}
|
|
||||||
|
|
||||||
l := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(l, uint16(len(p)))
|
|
||||||
|
|
||||||
n, err := (&net.Buffers{l, p}).WriteTo(co.Conn)
|
|
||||||
return int(n), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return co.Conn.Write(p)
|
if _, ok := co.Conn.(net.PacketConn); ok {
|
||||||
|
return co.Conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(l, uint16(len(p)))
|
||||||
|
|
||||||
|
n, err := (&net.Buffers{l, p}).WriteTo(co.Conn)
|
||||||
|
return int(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the appropriate timeout for a specific request
|
// Return the appropriate timeout for a specific request
|
||||||
|
|
|
@ -54,7 +54,7 @@ type RR interface {
|
||||||
// parse parses an RR from zone file format.
|
// parse parses an RR from zone file format.
|
||||||
//
|
//
|
||||||
// This will only be called on a new and empty RR type with only the header populated.
|
// This will only be called on a new and empty RR type with only the header populated.
|
||||||
parse(c *zlexer, origin, file string) *ParseError
|
parse(c *zlexer, origin string) *ParseError
|
||||||
|
|
||||||
// isDuplicate returns whether the two RRs are duplicates.
|
// isDuplicate returns whether the two RRs are duplicates.
|
||||||
isDuplicate(r2 RR) bool
|
isDuplicate(r2 RR) bool
|
||||||
|
@ -105,7 +105,7 @@ func (h *RR_Header) unpack(msg []byte, off int) (int, error) {
|
||||||
panic("dns: internal error: unpack should never be called on RR_Header")
|
panic("dns: internal error: unpack should never be called on RR_Header")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *RR_Header) parse(c *zlexer, origin, file string) *ParseError {
|
func (h *RR_Header) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on RR_Header")
|
panic("dns: internal error: parse should never be called on RR_Header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,8 +141,8 @@ func (k *DNSKEY) KeyTag() uint16 {
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case RSAMD5:
|
case RSAMD5:
|
||||||
// Look at the bottom two bytes of the modules, which the last
|
// Look at the bottom two bytes of the modules, which the last
|
||||||
// item in the pubkey. We could do this faster by looking directly
|
// item in the pubkey.
|
||||||
// at the base64 values. But I'm lazy.
|
// This algorithm has been deprecated, but keep this key-tag calculation.
|
||||||
modulus, _ := fromBase64([]byte(k.PublicKey))
|
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||||
if len(modulus) > 1 {
|
if len(modulus) > 1 {
|
||||||
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
||||||
|
@ -318,6 +318,9 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
rr.Signature = toBase64(signature)
|
rr.Signature = toBase64(signature)
|
||||||
|
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||||
|
// See RFC 6944.
|
||||||
|
return ErrAlg
|
||||||
default:
|
default:
|
||||||
h := hash.New()
|
h := hash.New()
|
||||||
h.Write(signdata)
|
h.Write(signdata)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
@ -20,11 +19,9 @@ import (
|
||||||
// bits should be set to the size of the algorithm.
|
// bits should be set to the size of the algorithm.
|
||||||
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case DSA, DSANSEC3SHA1:
|
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||||
if bits != 1024 {
|
return nil, ErrAlg
|
||||||
return nil, ErrKeySize
|
case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||||
}
|
|
||||||
case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
|
||||||
if bits < 512 || bits > 4096 {
|
if bits < 512 || bits > 4096 {
|
||||||
return nil, ErrKeySize
|
return nil, ErrKeySize
|
||||||
}
|
}
|
||||||
|
@ -47,20 +44,7 @@ func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch k.Algorithm {
|
switch k.Algorithm {
|
||||||
case DSA, DSANSEC3SHA1:
|
case RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||||
params := new(dsa.Parameters)
|
|
||||||
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
priv := new(dsa.PrivateKey)
|
|
||||||
priv.PublicKey.Parameters = *params
|
|
||||||
err := dsa.GenerateKey(priv, rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
|
|
||||||
return priv, nil
|
|
||||||
case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -120,16 +104,6 @@ func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the public key for DSA
|
|
||||||
func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
|
|
||||||
if _Q == nil || _P == nil || _G == nil || _Y == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
buf := dsaToBuf(_Q, _P, _G, _Y)
|
|
||||||
k.PublicKey = toBase64(buf)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the public key for Ed25519
|
// Set the public key for Ed25519
|
||||||
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
|
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
|
||||||
if _K == nil {
|
if _K == nil {
|
||||||
|
@ -164,15 +138,3 @@ func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
|
||||||
buf = append(buf, intToBytes(_Y, intlen)...)
|
buf = append(buf, intToBytes(_Y, intlen)...)
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the public key for X and Y for Curve. The two
|
|
||||||
// values are just concatenated.
|
|
||||||
func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
|
|
||||||
t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
|
|
||||||
buf := []byte{byte(t)}
|
|
||||||
buf = append(buf, intToBytes(_Q, 20)...)
|
|
||||||
buf = append(buf, intToBytes(_P, 64+t*8)...)
|
|
||||||
buf = append(buf, intToBytes(_G, 64+t*8)...)
|
|
||||||
buf = append(buf, intToBytes(_Y, 64+t*8)...)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package dns
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"io"
|
"io"
|
||||||
|
@ -44,19 +43,8 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
|
||||||
return nil, ErrPrivKey
|
return nil, ErrPrivKey
|
||||||
}
|
}
|
||||||
switch uint8(algo) {
|
switch uint8(algo) {
|
||||||
case DSA:
|
case RSAMD5, DSA, DSANSEC3SHA1:
|
||||||
priv, err := readPrivateKeyDSA(m)
|
return nil, ErrAlg
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pub := k.publicKeyDSA()
|
|
||||||
if pub == nil {
|
|
||||||
return nil, ErrKey
|
|
||||||
}
|
|
||||||
priv.PublicKey = *pub
|
|
||||||
return priv, nil
|
|
||||||
case RSAMD5:
|
|
||||||
fallthrough
|
|
||||||
case RSASHA1:
|
case RSASHA1:
|
||||||
fallthrough
|
fallthrough
|
||||||
case RSASHA1NSEC3SHA1:
|
case RSASHA1NSEC3SHA1:
|
||||||
|
@ -129,24 +117,6 @@ func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
|
|
||||||
p := new(dsa.PrivateKey)
|
|
||||||
p.X = new(big.Int)
|
|
||||||
for k, v := range m {
|
|
||||||
switch k {
|
|
||||||
case "private_value(x)":
|
|
||||||
v1, err := fromBase64([]byte(v))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.X.SetBytes(v1)
|
|
||||||
case "created", "publish", "activate":
|
|
||||||
/* not used in Go (yet) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
|
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
|
||||||
p := new(ecdsa.PrivateKey)
|
p := new(ecdsa.PrivateKey)
|
||||||
p.D = new(big.Int)
|
p.D = new(big.Int)
|
||||||
|
|
|
@ -83,7 +83,7 @@ with:
|
||||||
|
|
||||||
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
||||||
|
|
||||||
When this functions returns you will get dns message. A dns message consists
|
When this functions returns you will get DNS message. A DNS message consists
|
||||||
out of four sections.
|
out of four sections.
|
||||||
The question section: in.Question, the answer section: in.Answer,
|
The question section: in.Question, the answer section: in.Answer,
|
||||||
the authority section: in.Ns and the additional section: in.Extra.
|
the authority section: in.Ns and the additional section: in.Extra.
|
||||||
|
@ -221,7 +221,7 @@ RFC 6895 sets aside a range of type codes for private use. This range is 65,280
|
||||||
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
|
||||||
can be used, before requesting an official type code from IANA.
|
can be used, before requesting an official type code from IANA.
|
||||||
|
|
||||||
See https://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more
|
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
EDNS0
|
EDNS0
|
||||||
|
@ -238,9 +238,8 @@ Basic use pattern for creating an (empty) OPT RR:
|
||||||
|
|
||||||
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces.
|
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces.
|
||||||
Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and
|
Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and
|
||||||
EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note that these options
|
EDNS0_SUBNET (RFC 7871). Note that these options may be combined in an OPT RR.
|
||||||
may be combined in an OPT RR. Basic use pattern for a server to check if (and
|
Basic use pattern for a server to check if (and which) options are set:
|
||||||
which) options are set:
|
|
||||||
|
|
||||||
// o is a dns.OPT
|
// o is a dns.OPT
|
||||||
for _, s := range o.Option {
|
for _, s := range o.Option {
|
||||||
|
|
|
@ -5,7 +5,7 @@ package dns
|
||||||
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
|
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
|
||||||
// So this means the header data is equal *and* the RDATA is the same. Return true
|
// So this means the header data is equal *and* the RDATA is the same. Return true
|
||||||
// is so, otherwise false.
|
// is so, otherwise false.
|
||||||
// It's is a protocol violation to have identical RRs in a message.
|
// It's a protocol violation to have identical RRs in a message.
|
||||||
func IsDuplicate(r1, r2 RR) bool {
|
func IsDuplicate(r1, r2 RR) bool {
|
||||||
// Check whether the record header is identical.
|
// Check whether the record header is identical.
|
||||||
if !r1.Header().isDuplicate(r2.Header()) {
|
if !r1.Header().isDuplicate(r2.Header()) {
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (rr *OPT) len(off int, compression map[string]struct{}) int {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *OPT) parse(c *zlexer, origin, file string) *ParseError {
|
func (rr *OPT) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on OPT")
|
panic("dns: internal error: parse should never be called on OPT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.C
|
||||||
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
|
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
|
||||||
// an expiration on an update RR. This is helpful for clients that cannot clean
|
// an expiration on an update RR. This is helpful for clients that cannot clean
|
||||||
// up after themselves. This is a draft RFC and more information can be found at
|
// up after themselves. This is a draft RFC and more information can be found at
|
||||||
// http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
// https://tools.ietf.org/html/draft-sekar-dns-ul-02
|
||||||
//
|
//
|
||||||
// o := new(dns.OPT)
|
// o := new(dns.OPT)
|
||||||
// o.Hdr.Name = "."
|
// o.Hdr.Name = "."
|
||||||
|
@ -370,24 +370,36 @@ func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.C
|
||||||
// e.Lease = 120 // in seconds
|
// e.Lease = 120 // in seconds
|
||||||
// o.Option = append(o.Option, e)
|
// o.Option = append(o.Option, e)
|
||||||
type EDNS0_UL struct {
|
type EDNS0_UL struct {
|
||||||
Code uint16 // Always EDNS0UL
|
Code uint16 // Always EDNS0UL
|
||||||
Lease uint32
|
Lease uint32
|
||||||
|
KeyLease uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option implements the EDNS0 interface.
|
// Option implements the EDNS0 interface.
|
||||||
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
|
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
|
||||||
func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
|
func (e *EDNS0_UL) String() string { return fmt.Sprintf("%d %d", e.Lease, e.KeyLease) }
|
||||||
func (e *EDNS0_UL) copy() EDNS0 { return &EDNS0_UL{e.Code, e.Lease} }
|
func (e *EDNS0_UL) copy() EDNS0 { return &EDNS0_UL{e.Code, e.Lease, e.KeyLease} }
|
||||||
|
|
||||||
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
|
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
|
||||||
func (e *EDNS0_UL) pack() ([]byte, error) {
|
func (e *EDNS0_UL) pack() ([]byte, error) {
|
||||||
b := make([]byte, 4)
|
var b []byte
|
||||||
|
if e.KeyLease == 0 {
|
||||||
|
b = make([]byte, 4)
|
||||||
|
} else {
|
||||||
|
b = make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint32(b[4:], e.KeyLease)
|
||||||
|
}
|
||||||
binary.BigEndian.PutUint32(b, e.Lease)
|
binary.BigEndian.PutUint32(b, e.Lease)
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EDNS0_UL) unpack(b []byte) error {
|
func (e *EDNS0_UL) unpack(b []byte) error {
|
||||||
if len(b) < 4 {
|
switch len(b) {
|
||||||
|
case 4:
|
||||||
|
e.KeyLease = 0
|
||||||
|
case 8:
|
||||||
|
e.KeyLease = binary.BigEndian.Uint32(b[4:])
|
||||||
|
default:
|
||||||
return ErrBuf
|
return ErrBuf
|
||||||
}
|
}
|
||||||
e.Lease = binary.BigEndian.Uint32(b)
|
e.Lease = binary.BigEndian.Uint32(b)
|
||||||
|
@ -531,6 +543,10 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||||
|
if len(b) == 0 {
|
||||||
|
// zero-length EXPIRE query, see RFC 7314 Section 2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if len(b) < 4 {
|
if len(b) < 4 {
|
||||||
return ErrBuf
|
return ErrBuf
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
func Fuzz(data []byte) int {
|
func Fuzz(data []byte) int {
|
||||||
msg := new(Msg)
|
msg := new(Msg)
|
||||||
|
|
||||||
|
@ -16,7 +18,14 @@ func Fuzz(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func FuzzNewRR(data []byte) int {
|
func FuzzNewRR(data []byte) int {
|
||||||
if _, err := NewRR(string(data)); err != nil {
|
str := string(data)
|
||||||
|
// Do not fuzz lines that include the $INCLUDE keyword and hint the fuzzer
|
||||||
|
// at avoiding them.
|
||||||
|
// See GH#1025 for context.
|
||||||
|
if strings.Contains(strings.ToUpper(str), "$INCLUDE") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if _, err := NewRR(str); err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -49,11 +49,15 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return zp.setParseError("bad stop in $GENERATE range", l)
|
return zp.setParseError("bad stop in $GENERATE range", l)
|
||||||
}
|
}
|
||||||
if end < 0 || start < 0 || end < start {
|
if end < 0 || start < 0 || end < start || (end-start)/step > 65535 {
|
||||||
return zp.setParseError("bad range in $GENERATE range", l)
|
return zp.setParseError("bad range in $GENERATE range", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
zp.c.Next() // _BLANK
|
// _BLANK
|
||||||
|
l, ok := zp.c.Next()
|
||||||
|
if !ok || l.value != zBlank {
|
||||||
|
return zp.setParseError("garbage after $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a complete new string, which we then parse again.
|
// Create a complete new string, which we then parse again.
|
||||||
var s string
|
var s string
|
||||||
|
@ -81,6 +85,7 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||||
}
|
}
|
||||||
zp.sub = NewZoneParser(r, zp.origin, zp.file)
|
zp.sub = NewZoneParser(r, zp.origin, zp.file)
|
||||||
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
|
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
|
||||||
|
zp.sub.generateDisallowed = true
|
||||||
zp.sub.SetDefaultTTL(defaultTtl)
|
zp.sub.SetDefaultTTL(defaultTtl)
|
||||||
return zp.subNext()
|
return zp.subNext()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module github.com/miekg/dns
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
|
||||||
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
|
||||||
|
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
||||||
|
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0=
|
||||||
|
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0=
|
||||||
|
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||||
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
@ -126,20 +126,23 @@ func Split(s string) []int {
|
||||||
// The bool end is true when the end of the string has been reached.
|
// The bool end is true when the end of the string has been reached.
|
||||||
// Also see PrevLabel.
|
// Also see PrevLabel.
|
||||||
func NextLabel(s string, offset int) (i int, end bool) {
|
func NextLabel(s string, offset int) (i int, end bool) {
|
||||||
quote := false
|
if s == "" {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
for i = offset; i < len(s)-1; i++ {
|
for i = offset; i < len(s)-1; i++ {
|
||||||
switch s[i] {
|
if s[i] != '.' {
|
||||||
case '\\':
|
continue
|
||||||
quote = !quote
|
|
||||||
default:
|
|
||||||
quote = false
|
|
||||||
case '.':
|
|
||||||
if quote {
|
|
||||||
quote = !quote
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return i + 1, false
|
|
||||||
}
|
}
|
||||||
|
j := i - 1
|
||||||
|
for j >= 0 && s[j] == '\\' {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j-i)%2 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return i + 1, false
|
||||||
}
|
}
|
||||||
return i + 1, true
|
return i + 1, true
|
||||||
}
|
}
|
||||||
|
@ -149,17 +152,38 @@ func NextLabel(s string, offset int) (i int, end bool) {
|
||||||
// The bool start is true when the start of the string has been overshot.
|
// The bool start is true when the start of the string has been overshot.
|
||||||
// Also see NextLabel.
|
// Also see NextLabel.
|
||||||
func PrevLabel(s string, n int) (i int, start bool) {
|
func PrevLabel(s string, n int) (i int, start bool) {
|
||||||
|
if s == "" {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return len(s), false
|
return len(s), false
|
||||||
}
|
}
|
||||||
lab := Split(s)
|
|
||||||
if lab == nil {
|
l := len(s) - 1
|
||||||
return 0, true
|
if s[l] == '.' {
|
||||||
|
l--
|
||||||
}
|
}
|
||||||
if n > len(lab) {
|
|
||||||
return 0, true
|
for ; l >= 0 && n > 0; l-- {
|
||||||
|
if s[l] != '.' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
j := l - 1
|
||||||
|
for j >= 0 && s[j] == '\\' {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j-l)%2 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n--
|
||||||
|
if n == 0 {
|
||||||
|
return l + 1, false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return lab[len(lab)-n], false
|
|
||||||
|
return 0, n > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
||||||
|
|
|
@ -11,14 +11,12 @@ package dns
|
||||||
//go:generate go run msg_generate.go
|
//go:generate go run msg_generate.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -73,53 +71,23 @@ var (
|
||||||
ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication.
|
ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication.
|
||||||
)
|
)
|
||||||
|
|
||||||
// Id by default, returns a 16 bits random number to be used as a
|
// Id by default returns a 16-bit random number to be used as a message id. The
|
||||||
// message id. The random provided should be good enough. This being a
|
// number is drawn from a cryptographically secure random number generator.
|
||||||
// variable the function can be reassigned to a custom function.
|
// This being a variable the function can be reassigned to a custom function.
|
||||||
// For instance, to make it return a static value:
|
// For instance, to make it return a static value for testing:
|
||||||
//
|
//
|
||||||
// dns.Id = func() uint16 { return 3 }
|
// dns.Id = func() uint16 { return 3 }
|
||||||
var Id = id
|
var Id = id
|
||||||
|
|
||||||
var (
|
|
||||||
idLock sync.Mutex
|
|
||||||
idRand *rand.Rand
|
|
||||||
)
|
|
||||||
|
|
||||||
// id returns a 16 bits random number to be used as a
|
// id returns a 16 bits random number to be used as a
|
||||||
// message id. The random provided should be good enough.
|
// message id. The random provided should be good enough.
|
||||||
func id() uint16 {
|
func id() uint16 {
|
||||||
idLock.Lock()
|
var output uint16
|
||||||
|
err := binary.Read(rand.Reader, binary.BigEndian, &output)
|
||||||
if idRand == nil {
|
if err != nil {
|
||||||
// This (partially) works around
|
panic("dns: reading random id failed: " + err.Error())
|
||||||
// https://github.com/golang/go/issues/11833 by only
|
|
||||||
// seeding idRand upon the first call to id.
|
|
||||||
|
|
||||||
var seed int64
|
|
||||||
var buf [8]byte
|
|
||||||
|
|
||||||
if _, err := crand.Read(buf[:]); err == nil {
|
|
||||||
seed = int64(binary.LittleEndian.Uint64(buf[:]))
|
|
||||||
} else {
|
|
||||||
seed = rand.Int63()
|
|
||||||
}
|
|
||||||
|
|
||||||
idRand = rand.New(rand.NewSource(seed))
|
|
||||||
}
|
}
|
||||||
|
return output
|
||||||
// The call to idRand.Uint32 must be within the
|
|
||||||
// mutex lock because *rand.Rand is not safe for
|
|
||||||
// concurrent use.
|
|
||||||
//
|
|
||||||
// There is no added performance overhead to calling
|
|
||||||
// idRand.Uint32 inside a mutex lock over just
|
|
||||||
// calling rand.Uint32 as the global math/rand rng
|
|
||||||
// is internally protected by a sync.Mutex.
|
|
||||||
id := uint16(idRand.Uint32())
|
|
||||||
|
|
||||||
idLock.Unlock()
|
|
||||||
return id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgHdr is a a manually-unpacked version of (id, bits).
|
// MsgHdr is a a manually-unpacked version of (id, bits).
|
||||||
|
|
|
@ -265,24 +265,36 @@ func unpackString(msg []byte, off int) (string, int, error) {
|
||||||
return "", off, &Error{err: "overflow unpacking txt"}
|
return "", off, &Error{err: "overflow unpacking txt"}
|
||||||
}
|
}
|
||||||
l := int(msg[off])
|
l := int(msg[off])
|
||||||
if off+l+1 > len(msg) {
|
off++
|
||||||
|
if off+l > len(msg) {
|
||||||
return "", off, &Error{err: "overflow unpacking txt"}
|
return "", off, &Error{err: "overflow unpacking txt"}
|
||||||
}
|
}
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
s.Grow(l)
|
consumed := 0
|
||||||
for _, b := range msg[off+1 : off+1+l] {
|
for i, b := range msg[off : off+l] {
|
||||||
switch {
|
switch {
|
||||||
case b == '"' || b == '\\':
|
case b == '"' || b == '\\':
|
||||||
|
if consumed == 0 {
|
||||||
|
s.Grow(l * 2)
|
||||||
|
}
|
||||||
|
s.Write(msg[off+consumed : off+i])
|
||||||
s.WriteByte('\\')
|
s.WriteByte('\\')
|
||||||
s.WriteByte(b)
|
s.WriteByte(b)
|
||||||
|
consumed = i + 1
|
||||||
case b < ' ' || b > '~': // unprintable
|
case b < ' ' || b > '~': // unprintable
|
||||||
|
if consumed == 0 {
|
||||||
|
s.Grow(l * 2)
|
||||||
|
}
|
||||||
|
s.Write(msg[off+consumed : off+i])
|
||||||
s.WriteString(escapeByte(b))
|
s.WriteString(escapeByte(b))
|
||||||
default:
|
consumed = i + 1
|
||||||
s.WriteByte(b)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
off += 1 + l
|
if consumed == 0 { // no escaping needed
|
||||||
return s.String(), off, nil
|
return string(msg[off : off+l]), off + l, nil
|
||||||
|
}
|
||||||
|
s.Write(msg[off+consumed : off+l])
|
||||||
|
return s.String(), off + l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func packString(s string, msg []byte, off int) (int, error) {
|
func packString(s string, msg []byte, off int) (int, error) {
|
||||||
|
@ -433,6 +445,13 @@ Option:
|
||||||
}
|
}
|
||||||
edns = append(edns, e)
|
edns = append(edns, e)
|
||||||
off += int(optlen)
|
off += int(optlen)
|
||||||
|
case EDNS0EXPIRE:
|
||||||
|
e := new(EDNS0_EXPIRE)
|
||||||
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
edns = append(edns, e)
|
||||||
|
off += int(optlen)
|
||||||
case EDNS0UL:
|
case EDNS0UL:
|
||||||
e := new(EDNS0_UL)
|
e := new(EDNS0_UL)
|
||||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||||
|
@ -495,7 +514,7 @@ Option:
|
||||||
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||||
for _, el := range options {
|
for _, el := range options {
|
||||||
b, err := el.pack()
|
b, err := el.pack()
|
||||||
if err != nil || off+3 > len(msg) {
|
if err != nil || off+4 > len(msg) {
|
||||||
return len(msg), &Error{err: "overflow packing opt"}
|
return len(msg), &Error{err: "overflow packing opt"}
|
||||||
}
|
}
|
||||||
binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code
|
binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code
|
||||||
|
@ -587,6 +606,29 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
|
||||||
return nsec, off, nil
|
return nsec, off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// typeBitMapLen is a helper function which computes the "maximum" length of
|
||||||
|
// a the NSEC Type BitMap field.
|
||||||
|
func typeBitMapLen(bitmap []uint16) int {
|
||||||
|
var l int
|
||||||
|
var lastwindow, lastlength uint16
|
||||||
|
for _, t := range bitmap {
|
||||||
|
window := t / 256
|
||||||
|
length := (t-window*256)/8 + 1
|
||||||
|
if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
|
||||||
|
l += int(lastlength) + 2
|
||||||
|
lastlength = 0
|
||||||
|
}
|
||||||
|
if window < lastwindow || length < lastlength {
|
||||||
|
// packDataNsec would return Error{err: "nsec bits out of order"} here, but
|
||||||
|
// when computing the length, we want do be liberal.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastwindow, lastlength = window, length
|
||||||
|
}
|
||||||
|
l += int(lastlength) + 2
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
|
||||||
if len(bitmap) == 0 {
|
if len(bitmap) == 0 {
|
||||||
return off, nil
|
return off, nil
|
||||||
|
@ -646,3 +688,123 @@ func packDataDomainNames(names []string, msg []byte, off int, compression compre
|
||||||
}
|
}
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) {
|
||||||
|
var err error
|
||||||
|
for i := range data {
|
||||||
|
off, err = packDataAplPrefix(&data[i], msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) {
|
||||||
|
if len(p.Network.IP) != len(p.Network.Mask) {
|
||||||
|
return len(msg), &Error{err: "address and mask lengths don't match"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
prefix, _ := p.Network.Mask.Size()
|
||||||
|
addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8]
|
||||||
|
|
||||||
|
switch len(p.Network.IP) {
|
||||||
|
case net.IPv4len:
|
||||||
|
off, err = packUint16(1, msg, off)
|
||||||
|
case net.IPv6len:
|
||||||
|
off, err = packUint16(2, msg, off)
|
||||||
|
default:
|
||||||
|
err = &Error{err: "unrecognized address family"}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
|
||||||
|
off, err = packUint8(uint8(prefix), msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n uint8
|
||||||
|
if p.Negation {
|
||||||
|
n = 0x80
|
||||||
|
}
|
||||||
|
adflen := uint8(len(addr)) & 0x7f
|
||||||
|
off, err = packUint8(n|adflen, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return len(msg), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if off+len(addr) > len(msg) {
|
||||||
|
return len(msg), &Error{err: "overflow packing APL prefix"}
|
||||||
|
}
|
||||||
|
off += copy(msg[off:], addr)
|
||||||
|
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) {
|
||||||
|
var result []APLPrefix
|
||||||
|
for off < len(msg) {
|
||||||
|
prefix, end, err := unpackDataAplPrefix(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return nil, len(msg), err
|
||||||
|
}
|
||||||
|
off = end
|
||||||
|
result = append(result, prefix)
|
||||||
|
}
|
||||||
|
return result, off, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
|
||||||
|
family, off, err := unpackUint16(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||||
|
}
|
||||||
|
prefix, off, err := unpackUint8(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||||
|
}
|
||||||
|
nlen, off, err := unpackUint8(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip []byte
|
||||||
|
switch family {
|
||||||
|
case 1:
|
||||||
|
ip = make([]byte, net.IPv4len)
|
||||||
|
case 2:
|
||||||
|
ip = make([]byte, net.IPv6len)
|
||||||
|
default:
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"}
|
||||||
|
}
|
||||||
|
if int(prefix) > 8*len(ip) {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"}
|
||||||
|
}
|
||||||
|
|
||||||
|
afdlen := int(nlen & 0x7f)
|
||||||
|
if (int(prefix)+7)/8 != afdlen {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"}
|
||||||
|
}
|
||||||
|
if off+afdlen > len(msg) {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"}
|
||||||
|
}
|
||||||
|
off += copy(ip, msg[off:off+afdlen])
|
||||||
|
if prefix%8 > 0 {
|
||||||
|
last := ip[afdlen-1]
|
||||||
|
zero := uint8(0xff) >> (prefix % 8)
|
||||||
|
if last&zero > 0 {
|
||||||
|
return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return APLPrefix{
|
||||||
|
Negation: (nlen & 0x80) != 0,
|
||||||
|
Network: net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
|
||||||
|
},
|
||||||
|
}, off, nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,12 +4,17 @@ package dns
|
||||||
// size by removing records that exceed the requested size.
|
// size by removing records that exceed the requested size.
|
||||||
//
|
//
|
||||||
// It will first check if the reply fits without compression and then with
|
// It will first check if the reply fits without compression and then with
|
||||||
// compression. If it won't fit with compression, Scrub then walks the
|
// compression. If it won't fit with compression, Truncate then walks the
|
||||||
// record adding as many records as possible without exceeding the
|
// record adding as many records as possible without exceeding the
|
||||||
// requested buffer size.
|
// requested buffer size.
|
||||||
//
|
//
|
||||||
// The TC bit will be set if any answer records were excluded from the
|
// The TC bit will be set if any records were excluded from the message.
|
||||||
// message. This indicates to that the client should retry over TCP.
|
// This indicates to that the client should retry over TCP.
|
||||||
|
//
|
||||||
|
// According to RFC 2181, the TC bit should only be set if not all of the
|
||||||
|
// "required" RRs can be included in the response. Unfortunately, we have
|
||||||
|
// no way of knowing which RRs are required so we set the TC bit if any RR
|
||||||
|
// had to be omitted from the response.
|
||||||
//
|
//
|
||||||
// The appropriate buffer size can be retrieved from the requests OPT
|
// The appropriate buffer size can be retrieved from the requests OPT
|
||||||
// record, if present, and is transport specific otherwise. dns.MinMsgSize
|
// record, if present, and is transport specific otherwise. dns.MinMsgSize
|
||||||
|
@ -71,9 +76,9 @@ func (dns *Msg) Truncate(size int) {
|
||||||
l, numExtra = truncateLoop(dns.Extra, size, l, compression)
|
l, numExtra = truncateLoop(dns.Extra, size, l, compression)
|
||||||
}
|
}
|
||||||
|
|
||||||
// According to RFC 2181, the TC bit should only be set if not all
|
// See the function documentation for when we set this.
|
||||||
// of the answer RRs can be included in the response.
|
dns.Truncated = len(dns.Answer) > numAnswer ||
|
||||||
dns.Truncated = len(dns.Answer) > numAnswer
|
len(dns.Ns) > numNS || len(dns.Extra) > numExtra
|
||||||
|
|
||||||
dns.Answer = dns.Answer[:numAnswer]
|
dns.Answer = dns.Answer[:numAnswer]
|
||||||
dns.Ns = dns.Ns[:numNS]
|
dns.Ns = dns.Ns[:numNS]
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivateRdata is an interface used for implementing "Private Use" RR types, see
|
// PrivateRdata is an interface used for implementing "Private Use" RR types, see
|
||||||
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
// RFC 6895. This allows one to experiment with new RR types, without requesting an
|
||||||
|
@ -18,7 +15,7 @@ type PrivateRdata interface {
|
||||||
// Unpack is used when unpacking a private RR from a buffer.
|
// Unpack is used when unpacking a private RR from a buffer.
|
||||||
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
|
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
|
||||||
Unpack([]byte) (int, error)
|
Unpack([]byte) (int, error)
|
||||||
// Copy copies the Rdata.
|
// Copy copies the Rdata into the PrivateRdata argument.
|
||||||
Copy(PrivateRdata) error
|
Copy(PrivateRdata) error
|
||||||
// Len returns the length in octets of the Rdata.
|
// Len returns the length in octets of the Rdata.
|
||||||
Len() int
|
Len() int
|
||||||
|
@ -29,22 +26,8 @@ type PrivateRdata interface {
|
||||||
type PrivateRR struct {
|
type PrivateRR struct {
|
||||||
Hdr RR_Header
|
Hdr RR_Header
|
||||||
Data PrivateRdata
|
Data PrivateRdata
|
||||||
}
|
|
||||||
|
|
||||||
func mkPrivateRR(rrtype uint16) *PrivateRR {
|
generator func() PrivateRdata // for copy
|
||||||
// Panics if RR is not an instance of PrivateRR.
|
|
||||||
rrfunc, ok := TypeToRR[rrtype]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype))
|
|
||||||
}
|
|
||||||
|
|
||||||
anyrr := rrfunc()
|
|
||||||
rr, ok := anyrr.(*PrivateRR)
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr))
|
|
||||||
}
|
|
||||||
|
|
||||||
return rr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header return the RR header of r.
|
// Header return the RR header of r.
|
||||||
|
@ -61,13 +44,12 @@ func (r *PrivateRR) len(off int, compression map[string]struct{}) int {
|
||||||
|
|
||||||
func (r *PrivateRR) copy() RR {
|
func (r *PrivateRR) copy() RR {
|
||||||
// make new RR like this:
|
// make new RR like this:
|
||||||
rr := mkPrivateRR(r.Hdr.Rrtype)
|
rr := &PrivateRR{r.Hdr, r.generator(), r.generator}
|
||||||
rr.Hdr = r.Hdr
|
|
||||||
|
|
||||||
err := r.Data.Copy(rr.Data)
|
if err := r.Data.Copy(rr.Data); err != nil {
|
||||||
if err != nil {
|
panic("dns: got value that could not be used to copy Private rdata: " + err.Error())
|
||||||
panic("dns: got value that could not be used to copy Private rdata")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rr
|
return rr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +68,7 @@ func (r *PrivateRR) unpack(msg []byte, off int) (int, error) {
|
||||||
return off, err
|
return off, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PrivateRR) parse(c *zlexer, origin, file string) *ParseError {
|
func (r *PrivateRR) parse(c *zlexer, origin string) *ParseError {
|
||||||
var l lex
|
var l lex
|
||||||
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
|
text := make([]string, 0, 2) // could be 0..N elements, median is probably 1
|
||||||
Fetch:
|
Fetch:
|
||||||
|
@ -103,7 +85,7 @@ Fetch:
|
||||||
|
|
||||||
err := r.Data.Parse(text)
|
err := r.Data.Parse(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ParseError{file, err.Error(), l}
|
return &ParseError{"", err.Error(), l}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -116,7 +98,7 @@ func (r1 *PrivateRR) isDuplicate(r2 RR) bool { return false }
|
||||||
func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) {
|
func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) {
|
||||||
rtypestr = strings.ToUpper(rtypestr)
|
rtypestr = strings.ToUpper(rtypestr)
|
||||||
|
|
||||||
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} }
|
TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator(), generator} }
|
||||||
TypeToString[rtype] = rtypestr
|
TypeToString[rtype] = rtypestr
|
||||||
StringToType[rtypestr] = rtype
|
StringToType[rtypestr] = rtype
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ func ReadRR(r io.Reader, file string) (RR, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseZone reads a RFC 1035 style zonefile from r. It returns
|
// ParseZone reads a RFC 1035 style zonefile from r. It returns
|
||||||
// *Tokens on the returned channel, each consisting of either a
|
// Tokens on the returned channel, each consisting of either a
|
||||||
// parsed RR and optional comment or a nil RR and an error. The
|
// parsed RR and optional comment or a nil RR and an error. The
|
||||||
// channel is closed by ParseZone when the end of r is reached.
|
// channel is closed by ParseZone when the end of r is reached.
|
||||||
//
|
//
|
||||||
|
@ -143,7 +143,8 @@ func ReadRR(r io.Reader, file string) (RR, error) {
|
||||||
// origin, as if the file would start with an $ORIGIN directive.
|
// origin, as if the file would start with an $ORIGIN directive.
|
||||||
//
|
//
|
||||||
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
|
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
|
||||||
// supported.
|
// supported. Note that $GENERATE's range support up to a maximum of
|
||||||
|
// of 65535 steps.
|
||||||
//
|
//
|
||||||
// Basic usage pattern when reading from a string (z) containing the
|
// Basic usage pattern when reading from a string (z) containing the
|
||||||
// zone data:
|
// zone data:
|
||||||
|
@ -203,6 +204,7 @@ func parseZone(r io.Reader, origin, file string, t chan *Token) {
|
||||||
//
|
//
|
||||||
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
|
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
|
||||||
// supported. Although $INCLUDE is disabled by default.
|
// supported. Although $INCLUDE is disabled by default.
|
||||||
|
// Note that $GENERATE's range support up to a maximum of 65535 steps.
|
||||||
//
|
//
|
||||||
// Basic usage pattern when reading from a string (z) containing the
|
// Basic usage pattern when reading from a string (z) containing the
|
||||||
// zone data:
|
// zone data:
|
||||||
|
@ -246,6 +248,7 @@ type ZoneParser struct {
|
||||||
includeDepth uint8
|
includeDepth uint8
|
||||||
|
|
||||||
includeAllowed bool
|
includeAllowed bool
|
||||||
|
generateDisallowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewZoneParser returns an RFC 1035 style zonefile parser that reads
|
// NewZoneParser returns an RFC 1035 style zonefile parser that reads
|
||||||
|
@ -503,9 +506,8 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return zp.setParseError("expecting $TTL value, not this...", l)
|
return zp.setParseError("expecting $TTL value, not this...", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e := slurpRemainder(zp.c, zp.file); e != nil {
|
if err := slurpRemainder(zp.c); err != nil {
|
||||||
zp.parseErr = e
|
return zp.setParseError(err.err, err.lex)
|
||||||
return nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, ok := stringToTTL(l.token)
|
ttl, ok := stringToTTL(l.token)
|
||||||
|
@ -527,9 +529,8 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return zp.setParseError("expecting $ORIGIN value, not this...", l)
|
return zp.setParseError("expecting $ORIGIN value, not this...", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e := slurpRemainder(zp.c, zp.file); e != nil {
|
if err := slurpRemainder(zp.c); err != nil {
|
||||||
zp.parseErr = e
|
return zp.setParseError(err.err, err.lex)
|
||||||
return nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name, ok := toAbsoluteName(l.token, zp.origin)
|
name, ok := toAbsoluteName(l.token, zp.origin)
|
||||||
|
@ -547,6 +548,9 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
|
|
||||||
st = zExpectDirGenerate
|
st = zExpectDirGenerate
|
||||||
case zExpectDirGenerate:
|
case zExpectDirGenerate:
|
||||||
|
if zp.generateDisallowed {
|
||||||
|
return zp.setParseError("nested $GENERATE directive not allowed", l)
|
||||||
|
}
|
||||||
if l.value != zString {
|
if l.value != zString {
|
||||||
return zp.setParseError("expecting $GENERATE value, not this...", l)
|
return zp.setParseError("expecting $GENERATE value, not this...", l)
|
||||||
}
|
}
|
||||||
|
@ -650,19 +654,44 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
|
|
||||||
st = zExpectRdata
|
st = zExpectRdata
|
||||||
case zExpectRdata:
|
case zExpectRdata:
|
||||||
r, e := setRR(*h, zp.c, zp.origin, zp.file)
|
var rr RR
|
||||||
if e != nil {
|
if newFn, ok := TypeToRR[h.Rrtype]; ok && canParseAsRR(h.Rrtype) {
|
||||||
// If e.lex is nil than we have encounter a unknown RR type
|
rr = newFn()
|
||||||
// in that case we substitute our current lex token
|
*rr.Header() = *h
|
||||||
if e.lex.token == "" && e.lex.value == 0 {
|
} else {
|
||||||
e.lex = l // Uh, dirty
|
rr = &RFC3597{Hdr: *h}
|
||||||
}
|
|
||||||
|
|
||||||
zp.parseErr = e
|
|
||||||
return nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, true
|
_, isPrivate := rr.(*PrivateRR)
|
||||||
|
if !isPrivate && zp.c.Peek().token == "" {
|
||||||
|
// This is a dynamic update rr.
|
||||||
|
|
||||||
|
// TODO(tmthrgd): Previously slurpRemainder was only called
|
||||||
|
// for certain RR types, which may have been important.
|
||||||
|
if err := slurpRemainder(zp.c); err != nil {
|
||||||
|
return zp.setParseError(err.err, err.lex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rr, true
|
||||||
|
} else if l.value == zNewline {
|
||||||
|
return zp.setParseError("unexpected newline", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rr.parse(zp.c, zp.origin); err != nil {
|
||||||
|
// err is a concrete *ParseError without the file field set.
|
||||||
|
// The setParseError call below will construct a new
|
||||||
|
// *ParseError with file set to zp.file.
|
||||||
|
|
||||||
|
// If err.lex is nil than we have encounter an unknown RR type
|
||||||
|
// in that case we substitute our current lex token.
|
||||||
|
if err.lex == (lex{}) {
|
||||||
|
return zp.setParseError(err.err, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zp.setParseError(err.err, err.lex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rr, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,6 +700,18 @@ func (zp *ZoneParser) Next() (RR, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canParseAsRR returns true if the record type can be parsed as a
|
||||||
|
// concrete RR. It blacklists certain record types that must be parsed
|
||||||
|
// according to RFC 3597 because they lack a presentation format.
|
||||||
|
func canParseAsRR(rrtype uint16) bool {
|
||||||
|
switch rrtype {
|
||||||
|
case TypeANY, TypeNULL, TypeOPT, TypeTSIG:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type zlexer struct {
|
type zlexer struct {
|
||||||
br io.ByteReader
|
br io.ByteReader
|
||||||
|
|
||||||
|
@ -682,7 +723,8 @@ type zlexer struct {
|
||||||
comBuf string
|
comBuf string
|
||||||
comment string
|
comment string
|
||||||
|
|
||||||
l lex
|
l lex
|
||||||
|
cachedL *lex
|
||||||
|
|
||||||
brace int
|
brace int
|
||||||
quote bool
|
quote bool
|
||||||
|
@ -748,13 +790,37 @@ func (zl *zlexer) readByte() (byte, bool) {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (zl *zlexer) Peek() lex {
|
||||||
|
if zl.nextL {
|
||||||
|
return zl.l
|
||||||
|
}
|
||||||
|
|
||||||
|
l, ok := zl.Next()
|
||||||
|
if !ok {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
if zl.nextL {
|
||||||
|
// Cache l. Next returns zl.cachedL then zl.l.
|
||||||
|
zl.cachedL = &l
|
||||||
|
} else {
|
||||||
|
// In this case l == zl.l, so we just tell Next to return zl.l.
|
||||||
|
zl.nextL = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
func (zl *zlexer) Next() (lex, bool) {
|
func (zl *zlexer) Next() (lex, bool) {
|
||||||
l := &zl.l
|
l := &zl.l
|
||||||
if zl.nextL {
|
switch {
|
||||||
|
case zl.cachedL != nil:
|
||||||
|
l, zl.cachedL = zl.cachedL, nil
|
||||||
|
return *l, true
|
||||||
|
case zl.nextL:
|
||||||
zl.nextL = false
|
zl.nextL = false
|
||||||
return *l, true
|
return *l, true
|
||||||
}
|
case l.err:
|
||||||
if l.err {
|
|
||||||
// Parsing errors should be sticky.
|
// Parsing errors should be sticky.
|
||||||
return lex{value: zEOF}, false
|
return lex{value: zEOF}, false
|
||||||
}
|
}
|
||||||
|
@ -908,6 +974,11 @@ func (zl *zlexer) Next() (lex, bool) {
|
||||||
// was inside braces and we delayed adding it until now.
|
// was inside braces and we delayed adding it until now.
|
||||||
com[comi] = ' ' // convert newline to space
|
com[comi] = ' ' // convert newline to space
|
||||||
comi++
|
comi++
|
||||||
|
if comi >= len(com) {
|
||||||
|
l.token = "comment length insufficient for parsing"
|
||||||
|
l.err = true
|
||||||
|
return *l, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
com[comi] = ';'
|
com[comi] = ';'
|
||||||
|
@ -1302,18 +1373,18 @@ func locCheckEast(token string, longitude uint32) (uint32, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Eat" the rest of the "line"
|
// "Eat" the rest of the "line"
|
||||||
func slurpRemainder(c *zlexer, f string) *ParseError {
|
func slurpRemainder(c *zlexer) *ParseError {
|
||||||
l, _ := c.Next()
|
l, _ := c.Next()
|
||||||
switch l.value {
|
switch l.value {
|
||||||
case zBlank:
|
case zBlank:
|
||||||
l, _ = c.Next()
|
l, _ = c.Next()
|
||||||
if l.value != zNewline && l.value != zEOF {
|
if l.value != zNewline && l.value != zEOF {
|
||||||
return &ParseError{f, "garbage after rdata", l}
|
return &ParseError{"", "garbage after rdata", l}
|
||||||
}
|
}
|
||||||
case zNewline:
|
case zNewline:
|
||||||
case zEOF:
|
case zEOF:
|
||||||
default:
|
default:
|
||||||
return &ParseError{f, "garbage after rdata", l}
|
return &ParseError{"", "garbage after rdata", l}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -36,33 +36,9 @@ func (mux *ServeMux) match(q string, t uint16) Handler {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q = strings.ToLower(q)
|
||||||
|
|
||||||
var handler Handler
|
var handler Handler
|
||||||
|
|
||||||
// TODO(tmthrgd): Once https://go-review.googlesource.com/c/go/+/137575
|
|
||||||
// lands in a go release, replace the following with strings.ToLower.
|
|
||||||
var sb strings.Builder
|
|
||||||
for i := 0; i < len(q); i++ {
|
|
||||||
c := q[i]
|
|
||||||
if !(c >= 'A' && c <= 'Z') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Grow(len(q))
|
|
||||||
sb.WriteString(q[:i])
|
|
||||||
|
|
||||||
for ; i < len(q); i++ {
|
|
||||||
c := q[i]
|
|
||||||
if c >= 'A' && c <= 'Z' {
|
|
||||||
c += 'a' - 'A'
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteByte(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
q = sb.String()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for off, end := 0, false; !end; off, end = NextLabel(q, off) {
|
for off, end := 0, false; !end; off, end = NextLabel(q, off) {
|
||||||
if h, ok := mux.z[q[off:]]; ok {
|
if h, ok := mux.z[q[off:]]; ok {
|
||||||
if t != TypeDS {
|
if t != TypeDS {
|
||||||
|
|
|
@ -560,26 +560,32 @@ func (srv *Server) serveDNS(m []byte, w *response) {
|
||||||
req := new(Msg)
|
req := new(Msg)
|
||||||
req.setHdr(dh)
|
req.setHdr(dh)
|
||||||
|
|
||||||
switch srv.MsgAcceptFunc(dh) {
|
switch action := srv.MsgAcceptFunc(dh); action {
|
||||||
case MsgAccept:
|
case MsgAccept:
|
||||||
if req.unpack(dh, m, off) == nil {
|
if req.unpack(dh, m, off) == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fallthrough
|
fallthrough
|
||||||
case MsgReject:
|
case MsgReject, MsgRejectNotImplemented:
|
||||||
|
opcode := req.Opcode
|
||||||
req.SetRcodeFormatError(req)
|
req.SetRcodeFormatError(req)
|
||||||
|
req.Zero = false
|
||||||
|
if action == MsgRejectNotImplemented {
|
||||||
|
req.Opcode = opcode
|
||||||
|
req.Rcode = RcodeNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// Are we allowed to delete any OPT records here?
|
// Are we allowed to delete any OPT records here?
|
||||||
req.Ns, req.Answer, req.Extra = nil, nil, nil
|
req.Ns, req.Answer, req.Extra = nil, nil, nil
|
||||||
|
|
||||||
w.WriteMsg(req)
|
w.WriteMsg(req)
|
||||||
|
fallthrough
|
||||||
|
case MsgIgnore:
|
||||||
if w.udp != nil && cap(m) == srv.UDPSize {
|
if w.udp != nil && cap(m) == srv.UDPSize {
|
||||||
srv.udpPool.Put(m[:srv.UDPSize])
|
srv.udpPool.Put(m[:srv.UDPSize])
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
case MsgIgnore:
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ type TSIG struct {
|
||||||
// TSIG has no official presentation format, but this will suffice.
|
// TSIG has no official presentation format, but this will suffice.
|
||||||
|
|
||||||
func (rr *TSIG) String() string {
|
func (rr *TSIG) String() string {
|
||||||
s := "\n;; TSIG PSEUDOSECTION:\n"
|
s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
|
||||||
s += rr.Hdr.String() +
|
s += rr.Hdr.String() +
|
||||||
" " + rr.Algorithm +
|
" " + rr.Algorithm +
|
||||||
" " + tsigTimeToString(rr.TimeSigned) +
|
" " + tsigTimeToString(rr.TimeSigned) +
|
||||||
|
@ -54,7 +54,7 @@ func (rr *TSIG) String() string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *TSIG) parse(c *zlexer, origin, file string) *ParseError {
|
func (rr *TSIG) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on TSIG")
|
panic("dns: internal error: parse should never be called on TSIG")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -61,6 +62,7 @@ const (
|
||||||
TypeCERT uint16 = 37
|
TypeCERT uint16 = 37
|
||||||
TypeDNAME uint16 = 39
|
TypeDNAME uint16 = 39
|
||||||
TypeOPT uint16 = 41 // EDNS
|
TypeOPT uint16 = 41 // EDNS
|
||||||
|
TypeAPL uint16 = 42
|
||||||
TypeDS uint16 = 43
|
TypeDS uint16 = 43
|
||||||
TypeSSHFP uint16 = 44
|
TypeSSHFP uint16 = 44
|
||||||
TypeRRSIG uint16 = 46
|
TypeRRSIG uint16 = 46
|
||||||
|
@ -238,7 +240,7 @@ type ANY struct {
|
||||||
|
|
||||||
func (rr *ANY) String() string { return rr.Hdr.String() }
|
func (rr *ANY) String() string { return rr.Hdr.String() }
|
||||||
|
|
||||||
func (rr *ANY) parse(c *zlexer, origin, file string) *ParseError {
|
func (rr *ANY) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on ANY")
|
panic("dns: internal error: parse should never be called on ANY")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +255,7 @@ func (rr *NULL) String() string {
|
||||||
return ";" + rr.Hdr.String() + rr.Data
|
return ";" + rr.Hdr.String() + rr.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *NULL) parse(c *zlexer, origin, file string) *ParseError {
|
func (rr *NULL) parse(c *zlexer, origin string) *ParseError {
|
||||||
panic("dns: internal error: parse should never be called on NULL")
|
panic("dns: internal error: parse should never be called on NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,25 +440,54 @@ func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) }
|
||||||
|
|
||||||
func sprintName(s string) string {
|
func sprintName(s string) string {
|
||||||
var dst strings.Builder
|
var dst strings.Builder
|
||||||
dst.Grow(len(s))
|
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
|
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
|
||||||
dst.WriteString(s[i : i+2])
|
if dst.Len() != 0 {
|
||||||
|
dst.WriteString(s[i : i+2])
|
||||||
|
}
|
||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b, n := nextByte(s, i)
|
b, n := nextByte(s, i)
|
||||||
switch {
|
if n == 0 {
|
||||||
case n == 0:
|
i++
|
||||||
i++ // dangling back slash
|
continue
|
||||||
case b == '.':
|
}
|
||||||
dst.WriteByte('.')
|
if b == '.' {
|
||||||
|
if dst.Len() != 0 {
|
||||||
|
dst.WriteByte('.')
|
||||||
|
}
|
||||||
|
i += n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case ' ', '\'', '@', ';', '(', ')', '"', '\\': // additional chars to escape
|
||||||
|
if dst.Len() == 0 {
|
||||||
|
dst.Grow(len(s) * 2)
|
||||||
|
dst.WriteString(s[:i])
|
||||||
|
}
|
||||||
|
dst.WriteByte('\\')
|
||||||
|
dst.WriteByte(b)
|
||||||
default:
|
default:
|
||||||
writeDomainNameByte(&dst, b)
|
if ' ' <= b && b <= '~' {
|
||||||
|
if dst.Len() != 0 {
|
||||||
|
dst.WriteByte(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dst.Len() == 0 {
|
||||||
|
dst.Grow(len(s) * 2)
|
||||||
|
dst.WriteString(s[:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(escapeByte(b))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += n
|
i += n
|
||||||
}
|
}
|
||||||
|
if dst.Len() == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
return dst.String()
|
return dst.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,16 +541,6 @@ func sprintTxt(txt []string) string {
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDomainNameByte(s *strings.Builder, b byte) {
|
|
||||||
switch b {
|
|
||||||
case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape
|
|
||||||
s.WriteByte('\\')
|
|
||||||
s.WriteByte(b)
|
|
||||||
default:
|
|
||||||
writeTXTStringByte(s, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeTXTStringByte(s *strings.Builder, b byte) {
|
func writeTXTStringByte(s *strings.Builder, b byte) {
|
||||||
switch {
|
switch {
|
||||||
case b == '"' || b == '\\':
|
case b == '"' || b == '\\':
|
||||||
|
@ -854,14 +875,7 @@ func (rr *NSEC) String() string {
|
||||||
func (rr *NSEC) len(off int, compression map[string]struct{}) int {
|
func (rr *NSEC) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += domainNameLen(rr.NextDomain, off+l, compression, false)
|
l += domainNameLen(rr.NextDomain, off+l, compression, false)
|
||||||
lastwindow := uint32(2 ^ 32 + 1)
|
l += typeBitMapLen(rr.TypeBitMap)
|
||||||
for _, t := range rr.TypeBitMap {
|
|
||||||
window := t / 256
|
|
||||||
if uint32(window) != lastwindow {
|
|
||||||
l += 1 + 32
|
|
||||||
}
|
|
||||||
lastwindow = uint32(window)
|
|
||||||
}
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,14 +1034,7 @@ func (rr *NSEC3) String() string {
|
||||||
func (rr *NSEC3) len(off int, compression map[string]struct{}) int {
|
func (rr *NSEC3) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
|
l += 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1
|
||||||
lastwindow := uint32(2 ^ 32 + 1)
|
l += typeBitMapLen(rr.TypeBitMap)
|
||||||
for _, t := range rr.TypeBitMap {
|
|
||||||
window := t / 256
|
|
||||||
if uint32(window) != lastwindow {
|
|
||||||
l += 1 + 32
|
|
||||||
}
|
|
||||||
lastwindow = uint32(window)
|
|
||||||
}
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,17 +1351,92 @@ func (rr *CSYNC) String() string {
|
||||||
func (rr *CSYNC) len(off int, compression map[string]struct{}) int {
|
func (rr *CSYNC) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += 4 + 2
|
l += 4 + 2
|
||||||
lastwindow := uint32(2 ^ 32 + 1)
|
l += typeBitMapLen(rr.TypeBitMap)
|
||||||
for _, t := range rr.TypeBitMap {
|
|
||||||
window := t / 256
|
|
||||||
if uint32(window) != lastwindow {
|
|
||||||
l += 1 + 32
|
|
||||||
}
|
|
||||||
lastwindow = uint32(window)
|
|
||||||
}
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// APL RR. See RFC 3123.
|
||||||
|
type APL struct {
|
||||||
|
Hdr RR_Header
|
||||||
|
Prefixes []APLPrefix `dns:"apl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APLPrefix is an address prefix hold by an APL record.
|
||||||
|
type APLPrefix struct {
|
||||||
|
Negation bool
|
||||||
|
Network net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns presentation form of the APL record.
|
||||||
|
func (rr *APL) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(rr.Hdr.String())
|
||||||
|
for i, p := range rr.Prefixes {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
sb.WriteString(p.str())
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// str returns presentation form of the APL prefix.
|
||||||
|
func (p *APLPrefix) str() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if p.Negation {
|
||||||
|
sb.WriteByte('!')
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(p.Network.IP) {
|
||||||
|
case net.IPv4len:
|
||||||
|
sb.WriteByte('1')
|
||||||
|
case net.IPv6len:
|
||||||
|
sb.WriteByte('2')
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteByte(':')
|
||||||
|
|
||||||
|
switch len(p.Network.IP) {
|
||||||
|
case net.IPv4len:
|
||||||
|
sb.WriteString(p.Network.IP.String())
|
||||||
|
case net.IPv6len:
|
||||||
|
// add prefix for IPv4-mapped IPv6
|
||||||
|
if v4 := p.Network.IP.To4(); v4 != nil {
|
||||||
|
sb.WriteString("::ffff:")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Network.IP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteByte('/')
|
||||||
|
|
||||||
|
prefix, _ := p.Network.Mask.Size()
|
||||||
|
sb.WriteString(strconv.Itoa(prefix))
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// equals reports whether two APL prefixes are identical.
|
||||||
|
func (a *APLPrefix) equals(b *APLPrefix) bool {
|
||||||
|
return a.Negation == b.Negation &&
|
||||||
|
bytes.Equal(a.Network.IP, b.Network.IP) &&
|
||||||
|
bytes.Equal(a.Network.Mask, b.Network.Mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a copy of the APL prefix.
|
||||||
|
func (p *APLPrefix) copy() APLPrefix {
|
||||||
|
return APLPrefix{
|
||||||
|
Negation: p.Negation,
|
||||||
|
Network: copyNet(p.Network),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// len returns size of the prefix in wire format.
|
||||||
|
func (p *APLPrefix) len() int {
|
||||||
|
// 4-byte header and the network address prefix (see Section 4 of RFC 3123)
|
||||||
|
prefix, _ := p.Network.Mask.Size()
|
||||||
|
return 4 + (prefix+7)/8
|
||||||
|
}
|
||||||
|
|
||||||
// TimeToString translates the RRSIG's incep. and expir. times to the
|
// TimeToString translates the RRSIG's incep. and expir. times to the
|
||||||
// string representation used when printing the record.
|
// string representation used when printing the record.
|
||||||
// It takes serial arithmetic (RFC 1982) into account.
|
// It takes serial arithmetic (RFC 1982) into account.
|
||||||
|
@ -1411,6 +1493,17 @@ func copyIP(ip net.IP) net.IP {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyNet returns a copy of a subnet.
|
||||||
|
func copyNet(n net.IPNet) net.IPNet {
|
||||||
|
m := make(net.IPMask, len(n.Mask))
|
||||||
|
copy(m, n.Mask)
|
||||||
|
|
||||||
|
return net.IPNet{
|
||||||
|
IP: copyIP(n.IP),
|
||||||
|
Mask: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SplitN splits a string into N sized string chunks.
|
// SplitN splits a string into N sized string chunks.
|
||||||
// This might become an exported function once.
|
// This might become an exported function once.
|
||||||
func splitN(s string, n int) []string {
|
func splitN(s string, n int) []string {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package dns
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Version is current version of this library.
|
// Version is current version of this library.
|
||||||
var Version = V{1, 1, 8}
|
var Version = V{1, 1, 27}
|
||||||
|
|
||||||
// V holds the version of this library.
|
// V holds the version of this library.
|
||||||
type V struct {
|
type V struct {
|
||||||
|
|
|
@ -182,14 +182,17 @@ func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
|
||||||
//
|
//
|
||||||
// ch := make(chan *dns.Envelope)
|
// ch := make(chan *dns.Envelope)
|
||||||
// tr := new(dns.Transfer)
|
// tr := new(dns.Transfer)
|
||||||
// go tr.Out(w, r, ch)
|
// var wg sync.WaitGroup
|
||||||
|
// go func() {
|
||||||
|
// tr.Out(w, r, ch)
|
||||||
|
// wg.Done()
|
||||||
|
// }()
|
||||||
// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
|
// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
|
||||||
// close(ch)
|
// close(ch)
|
||||||
// w.Hijack()
|
// wg.Wait() // wait until everything is written out
|
||||||
// // w.Close() // Client closes connection
|
// w.Close() // close connection
|
||||||
//
|
//
|
||||||
// The server is responsible for sending the correct sequence of RRs through the
|
// The server is responsible for sending the correct sequence of RRs through the channel ch.
|
||||||
// channel ch.
|
|
||||||
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
|
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
|
||||||
for x := range ch {
|
for x := range ch {
|
||||||
r := new(Msg)
|
r := new(Msg)
|
||||||
|
@ -198,11 +201,14 @@ func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
|
||||||
r.Authoritative = true
|
r.Authoritative = true
|
||||||
// assume it fits TODO(miek): fix
|
// assume it fits TODO(miek): fix
|
||||||
r.Answer = append(r.Answer, x.RR...)
|
r.Answer = append(r.Answer, x.RR...)
|
||||||
|
if tsig := q.IsTsig(); tsig != nil && w.TsigStatus() == nil {
|
||||||
|
r.SetTsig(tsig.Hdr.Name, tsig.Algorithm, tsig.Fudge, time.Now().Unix())
|
||||||
|
}
|
||||||
if err := w.WriteMsg(r); err != nil {
|
if err := w.WriteMsg(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
w.TsigTimersOnly(true)
|
||||||
}
|
}
|
||||||
w.TsigTimersOnly(true)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,23 @@ func (r1 *ANY) isDuplicate(_r2 RR) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r1 *APL) isDuplicate(_r2 RR) bool {
|
||||||
|
r2, ok := _r2.(*APL)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = r2
|
||||||
|
if len(r1.Prefixes) != len(r2.Prefixes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(r1.Prefixes); i++ {
|
||||||
|
if !r1.Prefixes[i].equals(&r2.Prefixes[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r1 *AVC) isDuplicate(_r2 RR) bool {
|
func (r1 *AVC) isDuplicate(_r2 RR) bool {
|
||||||
r2, ok := _r2.(*AVC)
|
r2, ok := _r2.(*AVC)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -36,6 +36,14 @@ func (rr *ANY) pack(msg []byte, off int, compression compressionMap, compress bo
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *APL) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
|
off, err = packDataApl(rr.Prefixes, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *AVC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
func (rr *AVC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
|
||||||
off, err = packStringTxt(rr.Txt, msg, off)
|
off, err = packStringTxt(rr.Txt, msg, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1127,6 +1135,17 @@ func (rr *ANY) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
return off, nil
|
return off, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rr *APL) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
|
rdStart := off
|
||||||
|
_ = rdStart
|
||||||
|
|
||||||
|
rr.Prefixes, off, err = unpackDataApl(msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rr *AVC) unpack(msg []byte, off int) (off1 int, err error) {
|
func (rr *AVC) unpack(msg []byte, off int) (off1 int, err error) {
|
||||||
rdStart := off
|
rdStart := off
|
||||||
_ = rdStart
|
_ = rdStart
|
||||||
|
|
|
@ -13,6 +13,7 @@ var TypeToRR = map[uint16]func() RR{
|
||||||
TypeAAAA: func() RR { return new(AAAA) },
|
TypeAAAA: func() RR { return new(AAAA) },
|
||||||
TypeAFSDB: func() RR { return new(AFSDB) },
|
TypeAFSDB: func() RR { return new(AFSDB) },
|
||||||
TypeANY: func() RR { return new(ANY) },
|
TypeANY: func() RR { return new(ANY) },
|
||||||
|
TypeAPL: func() RR { return new(APL) },
|
||||||
TypeAVC: func() RR { return new(AVC) },
|
TypeAVC: func() RR { return new(AVC) },
|
||||||
TypeCAA: func() RR { return new(CAA) },
|
TypeCAA: func() RR { return new(CAA) },
|
||||||
TypeCDNSKEY: func() RR { return new(CDNSKEY) },
|
TypeCDNSKEY: func() RR { return new(CDNSKEY) },
|
||||||
|
@ -87,6 +88,7 @@ var TypeToString = map[uint16]string{
|
||||||
TypeAAAA: "AAAA",
|
TypeAAAA: "AAAA",
|
||||||
TypeAFSDB: "AFSDB",
|
TypeAFSDB: "AFSDB",
|
||||||
TypeANY: "ANY",
|
TypeANY: "ANY",
|
||||||
|
TypeAPL: "APL",
|
||||||
TypeATMA: "ATMA",
|
TypeATMA: "ATMA",
|
||||||
TypeAVC: "AVC",
|
TypeAVC: "AVC",
|
||||||
TypeAXFR: "AXFR",
|
TypeAXFR: "AXFR",
|
||||||
|
@ -169,6 +171,7 @@ func (rr *A) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *AAAA) Header() *RR_Header { return &rr.Hdr }
|
func (rr *AAAA) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr }
|
func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *ANY) Header() *RR_Header { return &rr.Hdr }
|
func (rr *ANY) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
func (rr *APL) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *AVC) Header() *RR_Header { return &rr.Hdr }
|
func (rr *AVC) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *CAA) Header() *RR_Header { return &rr.Hdr }
|
func (rr *CAA) Header() *RR_Header { return &rr.Hdr }
|
||||||
func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr }
|
func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr }
|
||||||
|
@ -262,6 +265,13 @@ func (rr *ANY) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
func (rr *APL) len(off int, compression map[string]struct{}) int {
|
||||||
|
l := rr.Hdr.len(off, compression)
|
||||||
|
for _, x := range rr.Prefixes {
|
||||||
|
l += x.len()
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
func (rr *AVC) len(off int, compression map[string]struct{}) int {
|
func (rr *AVC) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
for _, x := range rr.Txt {
|
for _, x := range rr.Txt {
|
||||||
|
@ -312,12 +322,12 @@ func (rr *DS) len(off int, compression map[string]struct{}) int {
|
||||||
l += 2 // KeyTag
|
l += 2 // KeyTag
|
||||||
l++ // Algorithm
|
l++ // Algorithm
|
||||||
l++ // DigestType
|
l++ // DigestType
|
||||||
l += len(rr.Digest)/2 + 1
|
l += len(rr.Digest) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *EID) len(off int, compression map[string]struct{}) int {
|
func (rr *EID) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += len(rr.Endpoint)/2 + 1
|
l += len(rr.Endpoint) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *EUI48) len(off int, compression map[string]struct{}) int {
|
func (rr *EUI48) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -452,7 +462,7 @@ func (rr *NID) len(off int, compression map[string]struct{}) int {
|
||||||
}
|
}
|
||||||
func (rr *NIMLOC) len(off int, compression map[string]struct{}) int {
|
func (rr *NIMLOC) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += len(rr.Locator)/2 + 1
|
l += len(rr.Locator) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *NINFO) len(off int, compression map[string]struct{}) int {
|
func (rr *NINFO) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -505,7 +515,7 @@ func (rr *PX) len(off int, compression map[string]struct{}) int {
|
||||||
}
|
}
|
||||||
func (rr *RFC3597) len(off int, compression map[string]struct{}) int {
|
func (rr *RFC3597) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l += len(rr.Rdata)/2 + 1
|
l += len(rr.Rdata) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *RKEY) len(off int, compression map[string]struct{}) int {
|
func (rr *RKEY) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -546,7 +556,7 @@ func (rr *SMIMEA) len(off int, compression map[string]struct{}) int {
|
||||||
l++ // Usage
|
l++ // Usage
|
||||||
l++ // Selector
|
l++ // Selector
|
||||||
l++ // MatchingType
|
l++ // MatchingType
|
||||||
l += len(rr.Certificate)/2 + 1
|
l += len(rr.Certificate) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *SOA) len(off int, compression map[string]struct{}) int {
|
func (rr *SOA) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -579,7 +589,7 @@ func (rr *SSHFP) len(off int, compression map[string]struct{}) int {
|
||||||
l := rr.Hdr.len(off, compression)
|
l := rr.Hdr.len(off, compression)
|
||||||
l++ // Algorithm
|
l++ // Algorithm
|
||||||
l++ // Type
|
l++ // Type
|
||||||
l += len(rr.FingerPrint)/2 + 1
|
l += len(rr.FingerPrint) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *TA) len(off int, compression map[string]struct{}) int {
|
func (rr *TA) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -587,7 +597,7 @@ func (rr *TA) len(off int, compression map[string]struct{}) int {
|
||||||
l += 2 // KeyTag
|
l += 2 // KeyTag
|
||||||
l++ // Algorithm
|
l++ // Algorithm
|
||||||
l++ // DigestType
|
l++ // DigestType
|
||||||
l += len(rr.Digest)/2 + 1
|
l += len(rr.Digest) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *TALINK) len(off int, compression map[string]struct{}) int {
|
func (rr *TALINK) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -614,7 +624,7 @@ func (rr *TLSA) len(off int, compression map[string]struct{}) int {
|
||||||
l++ // Usage
|
l++ // Usage
|
||||||
l++ // Selector
|
l++ // Selector
|
||||||
l++ // MatchingType
|
l++ // MatchingType
|
||||||
l += len(rr.Certificate)/2 + 1
|
l += len(rr.Certificate) / 2
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
func (rr *TSIG) len(off int, compression map[string]struct{}) int {
|
func (rr *TSIG) len(off int, compression map[string]struct{}) int {
|
||||||
|
@ -673,6 +683,13 @@ func (rr *AFSDB) copy() RR {
|
||||||
func (rr *ANY) copy() RR {
|
func (rr *ANY) copy() RR {
|
||||||
return &ANY{rr.Hdr}
|
return &ANY{rr.Hdr}
|
||||||
}
|
}
|
||||||
|
func (rr *APL) copy() RR {
|
||||||
|
Prefixes := make([]APLPrefix, len(rr.Prefixes))
|
||||||
|
for i := range rr.Prefixes {
|
||||||
|
Prefixes[i] = rr.Prefixes[i].copy()
|
||||||
|
}
|
||||||
|
return &APL{rr.Hdr, Prefixes}
|
||||||
|
}
|
||||||
func (rr *AVC) copy() RR {
|
func (rr *AVC) copy() RR {
|
||||||
Txt := make([]string, len(rr.Txt))
|
Txt := make([]string, len(rr.Txt))
|
||||||
copy(Txt, rr.Txt)
|
copy(Txt, rr.Txt)
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.9.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2018 Mitchell Hashimoto
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,96 +0,0 @@
|
||||||
# HTTP Server-Timing for Go
|
|
||||||
[![Godoc](https://godoc.org/github.com/mitchellh/go-server-timing?status.svg)](https://godoc.org/github.com/mitchellh/go-server-timing)
|
|
||||||
|
|
||||||
This is a library including middleware for using
|
|
||||||
[HTTP Server-Timing](https://www.w3.org/TR/server-timing) with Go. This header
|
|
||||||
allows a server to send timing information from the backend, such as database
|
|
||||||
access time, file reads, etc. The timing information can be then be inspected
|
|
||||||
in the standard browser developer tools:
|
|
||||||
|
|
||||||
![Server Timing Example](https://raw.githubusercontent.com/mitchellh/go-server-timing/master/example/screenshot.png)
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* Middleware for injecting the server timing struct into the request `Context`
|
|
||||||
and writing the `Server-Timing` header.
|
|
||||||
|
|
||||||
* Concurrency-safe structures for easily recording timings of multiple
|
|
||||||
concurrency tasks.
|
|
||||||
|
|
||||||
* Parse `Server-Timing` headers as a client.
|
|
||||||
|
|
||||||
* Note: No browser properly supports sending the Server-Timing header as
|
|
||||||
an [HTTP Trailer](https://tools.ietf.org/html/rfc7230#section-4.4) so
|
|
||||||
the Middleware only supports a normal header currently.
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
|
|
||||||
Browser support is required to **view** server timings easily. Because server
|
|
||||||
timings are sent as an HTTP header, there is no negative impact to sending
|
|
||||||
the header to unsupported browsers.
|
|
||||||
|
|
||||||
* **Chrome 65 or higher** is required to properly display server timings
|
|
||||||
in the devtools.
|
|
||||||
|
|
||||||
* **Firefox is pending** with an [open bug report (ID 1403051)](https://bugzilla.mozilla.org/show_bug.cgi?id=1403051)
|
|
||||||
|
|
||||||
* IE, Opera, and others are unknown at this time.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Example usage is shown below. A fully runnable example is available in
|
|
||||||
the `example/` directory.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
// Our handler. In a real application this might be your root router,
|
|
||||||
// or some subset of your router. Wrapping this ensures that all routes
|
|
||||||
// handled by this handler have access to the server timing header struct.
|
|
||||||
var h http.Handler = http.HandlerFunc(handler)
|
|
||||||
|
|
||||||
// Wrap our handler with the server timing middleware
|
|
||||||
h = servertiming.Middleware(h, nil)
|
|
||||||
|
|
||||||
// Start!
|
|
||||||
http.ListenAndServe(":8080", h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get our timing header builder from the context
|
|
||||||
timing := servertiming.FromContext(r.Context())
|
|
||||||
|
|
||||||
// Imagine your handler performs some tasks in a goroutine, such as
|
|
||||||
// accessing some remote service. timing is concurrency safe so we can
|
|
||||||
// record how long that takes. Let's simulate making 5 concurrent requests
|
|
||||||
// to various servicse.
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
name := fmt.Sprintf("service-%d", i)
|
|
||||||
go func(name string) {
|
|
||||||
// This creats a new metric and starts the timer. The Stop is
|
|
||||||
// deferred so when the function exits it'll record the duration.
|
|
||||||
defer timing.NewMetric(name).Start().Stop()
|
|
||||||
time.Sleep(random(25, 75))
|
|
||||||
wg.Done()
|
|
||||||
}(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Imagine this is just some blocking code in your main handler such
|
|
||||||
// as a SQL query. Let's record that.
|
|
||||||
m := timing.NewMetric("sql").WithDesc("SQL query").Start()
|
|
||||||
time.Sleep(random(20, 50))
|
|
||||||
m.Stop()
|
|
||||||
|
|
||||||
// Wait for the goroutine to end
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// You could continue recording more metrics, but let's just return now
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Write([]byte("Done. Check your browser inspector timing details."))
|
|
||||||
}
|
|
||||||
|
|
||||||
func random(min, max int) time.Duration {
|
|
||||||
return (time.Duration(rand.Intn(max-min) + min)) * time.Millisecond
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,23 +0,0 @@
|
||||||
package servertiming
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewContext returns a new Context that carries the Header value h.
|
|
||||||
func NewContext(ctx context.Context, h *Header) context.Context {
|
|
||||||
return context.WithValue(ctx, contextKey, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromContext returns the *Header in the context, if any. If no Header
|
|
||||||
// value exists, nil is returned.
|
|
||||||
func FromContext(ctx context.Context) *Header {
|
|
||||||
h, _ := ctx.Value(contextKey).(*Header)
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
type contextKeyType struct{}
|
|
||||||
|
|
||||||
// The key where the header value is stored. This is globally unique since
|
|
||||||
// it uses a custom unexported type. The struct{} costs zero allocations.
|
|
||||||
var contextKey = contextKeyType(struct{}{})
|
|
|
@ -1,6 +0,0 @@
|
||||||
module github.com/mitchellh/go-server-timing
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/felixge/httpsnoop v1.0.0
|
|
||||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5
|
|
||||||
)
|
|
|
@ -1,4 +0,0 @@
|
||||||
github.com/felixge/httpsnoop v1.0.0 h1:gh8fMGz0rlOv/1WmRZm7OgncIOTsAj21iNJot48omJQ=
|
|
||||||
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
|
|
||||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5 h1:yrv1uUvgXH/tEat+wdvJMRJ4g51GlIydtDpU9pFjaaI=
|
|
||||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
|
|
|
@ -1,129 +0,0 @@
|
||||||
package servertiming
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/gddo/httputil/header"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HeaderKey is the specified key for the Server-Timing header.
|
|
||||||
const HeaderKey = "Server-Timing"
|
|
||||||
|
|
||||||
// Header represents a collection of metrics that can be encoded as
|
|
||||||
// a Server-Timing header value.
|
|
||||||
//
|
|
||||||
// The functions for working with metrics are concurrency-safe to make
|
|
||||||
// it easy to record metrics from goroutines. If you want to avoid the
|
|
||||||
// lock overhead, you can access the Metrics field directly.
|
|
||||||
//
|
|
||||||
// The functions for working with metrics are also usable on a nil
|
|
||||||
// Header pointer. This allows functions that use FromContext to get the
|
|
||||||
// *Header value to skip nil-checking and use it as normal. On a nil
|
|
||||||
// *Header, Metrics are not recorded.
|
|
||||||
type Header struct {
|
|
||||||
// Metrics is the list of metrics in the header.
|
|
||||||
Metrics []*Metric
|
|
||||||
|
|
||||||
// The lock that is held when Metrics is being modified. This
|
|
||||||
// ONLY NEEDS TO BE SET WHEN working with Metrics directly. If using
|
|
||||||
// the functions on the struct, the lock is managed automatically.
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHeader parses a Server-Timing header value.
|
|
||||||
func ParseHeader(input string) (*Header, error) {
|
|
||||||
// Split the comma-separated list of metrics
|
|
||||||
rawMetrics := header.ParseList(headerParams(input))
|
|
||||||
|
|
||||||
// Parse the list of metrics. We can pre-allocate the length of the
|
|
||||||
// comma-separated list of metrics since at most it will be that and
|
|
||||||
// most likely it will be that length.
|
|
||||||
metrics := make([]*Metric, 0, len(rawMetrics))
|
|
||||||
for _, raw := range rawMetrics {
|
|
||||||
var m Metric
|
|
||||||
m.Name, m.Extra = header.ParseValueAndParams(headerParams(raw))
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if v, ok := m.Extra[paramNameDesc]; ok {
|
|
||||||
m.Desc = v
|
|
||||||
delete(m.Extra, paramNameDesc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration. This is treated as a millisecond value since that
|
|
||||||
// is what modern browsers are treating it as. If the parsing of
|
|
||||||
// an integer fails, the set value remains in the Extra field.
|
|
||||||
if v, ok := m.Extra[paramNameDur]; ok {
|
|
||||||
m.Duration, _ = time.ParseDuration(v + "ms")
|
|
||||||
delete(m.Extra, paramNameDur)
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics = append(metrics, &m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Header{Metrics: metrics}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMetric creates a new Metric and adds it to this header.
|
|
||||||
func (h *Header) NewMetric(name string) *Metric {
|
|
||||||
return h.Add(&Metric{Name: name})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the given metric to the header.
|
|
||||||
//
|
|
||||||
// This function is safe to call concurrently.
|
|
||||||
func (h *Header) Add(m *Metric) *Metric {
|
|
||||||
if h == nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.Metrics = append(h.Metrics, m)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the valid Server-Timing header value that can be
|
|
||||||
// sent in an HTTP response.
|
|
||||||
func (h *Header) String() string {
|
|
||||||
parts := make([]string, 0, len(h.Metrics))
|
|
||||||
for _, m := range h.Metrics {
|
|
||||||
parts = append(parts, m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specified server-timing-param-name values.
|
|
||||||
const (
|
|
||||||
paramNameDesc = "desc"
|
|
||||||
paramNameDur = "dur"
|
|
||||||
)
|
|
||||||
|
|
||||||
// headerParams is a helper function that takes a header value and turns
|
|
||||||
// it into the expected argument format for the httputil/header library
|
|
||||||
// functions..
|
|
||||||
func headerParams(s string) (http.Header, string) {
|
|
||||||
const key = "Key"
|
|
||||||
return http.Header(map[string][]string{
|
|
||||||
key: {s},
|
|
||||||
}), key
|
|
||||||
}
|
|
||||||
|
|
||||||
var reNumber = regexp.MustCompile(`^\d+\.?\d*$`)
|
|
||||||
|
|
||||||
// headerEncodeParam encodes a key/value pair as a proper `key=value`
|
|
||||||
// syntax, using double-quotes if necessary.
|
|
||||||
func headerEncodeParam(key, value string) string {
|
|
||||||
// The only case we currently don't quote is numbers. We can make this
|
|
||||||
// smarter in the future.
|
|
||||||
if reNumber.MatchString(value) {
|
|
||||||
return fmt.Sprintf(`%s=%s`, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`%s=%q`, key, value)
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
package servertiming
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Metric represents a single metric for the Server-Timing header.
|
|
||||||
//
|
|
||||||
// The easiest way to use the Metric is to use NewMetric and chain it. This
|
|
||||||
// results in a single line defer at the top of a function time a function.
|
|
||||||
//
|
|
||||||
// timing := FromContext(r.Context())
|
|
||||||
// defer timing.NewMetric("sql").Start().Stop()
|
|
||||||
//
|
|
||||||
// For timing around specific blocks of code:
|
|
||||||
//
|
|
||||||
// m := timing.NewMetric("sql").Start()
|
|
||||||
// // ... run your code being timed here
|
|
||||||
// m.Stop()
|
|
||||||
//
|
|
||||||
// A metric is expected to represent a single timing event. Therefore,
|
|
||||||
// no functions on the struct are safe for concurrency by default. If a single
|
|
||||||
// Metric is shared by multiple concurrenty goroutines, you must lock access
|
|
||||||
// manually.
|
|
||||||
type Metric struct {
|
|
||||||
// Name is the name of the metric. This must be a valid RFC7230 "token"
|
|
||||||
// format. In a gist, this is an alphanumeric string that may contain
|
|
||||||
// most common symbols but may not contain any whitespace. The exact
|
|
||||||
// syntax can be found in RFC7230.
|
|
||||||
//
|
|
||||||
// It is common for Name to be a unique identifier (such as "sql-1") and
|
|
||||||
// for a more human-friendly name to be used in the "desc" field.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Duration is the duration of this Metric.
|
|
||||||
Duration time.Duration
|
|
||||||
|
|
||||||
// Desc is any string describing this metric. For example: "SQL Primary".
|
|
||||||
// The specific format of this is `token | quoted-string` according to
|
|
||||||
// RFC7230.
|
|
||||||
Desc string
|
|
||||||
|
|
||||||
// Extra is a set of extra parameters and values to send with the
|
|
||||||
// metric. The specification states that unrecognized parameters are
|
|
||||||
// to be ignored so it should be safe to add additional data here. The
|
|
||||||
// key must be a valid "token" (same syntax as Name) and the value can
|
|
||||||
// be any "token | quoted-string" (same as Desc field).
|
|
||||||
//
|
|
||||||
// If this map contains a key that would be sent by another field in this
|
|
||||||
// struct (such as "desc"), then this value is prioritized over the
|
|
||||||
// struct value.
|
|
||||||
Extra map[string]string
|
|
||||||
|
|
||||||
// startTime is the time that this metric recording was started if
|
|
||||||
// Start() was called.
|
|
||||||
startTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDesc is a chaining-friendly helper to set the Desc field on the Metric.
|
|
||||||
func (m *Metric) WithDesc(desc string) *Metric {
|
|
||||||
m.Desc = desc
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts a timer for recording the duration of some task. This must
|
|
||||||
// be paired with a Stop call to set the duration. Calling this again will
|
|
||||||
// reset the start time for a subsequent Stop call.
|
|
||||||
func (m *Metric) Start() *Metric {
|
|
||||||
m.startTime = time.Now()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop ends the timer started with Start and records the duration in the
|
|
||||||
// Duration field. Calling this multiple times will modify the Duration based
|
|
||||||
// on the last time Start was called.
|
|
||||||
//
|
|
||||||
// If Start was never called, this function has zero effect.
|
|
||||||
func (m *Metric) Stop() *Metric {
|
|
||||||
// Only record if we have a start time set with Start()
|
|
||||||
if !m.startTime.IsZero() {
|
|
||||||
m.Duration = time.Since(m.startTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the valid Server-Timing metric entry value.
|
|
||||||
func (m *Metric) String() string {
|
|
||||||
// Begin building parts, expected capacity is length of extra
|
|
||||||
// fields plus id, desc, dur.
|
|
||||||
parts := make([]string, 1, len(m.Extra)+3)
|
|
||||||
parts[0] = m.Name
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if _, ok := m.Extra[paramNameDesc]; !ok && m.Desc != "" {
|
|
||||||
parts = append(parts, headerEncodeParam(paramNameDesc, m.Desc))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration
|
|
||||||
if _, ok := m.Extra[paramNameDur]; !ok && m.Duration > 0 {
|
|
||||||
parts = append(parts, headerEncodeParam(
|
|
||||||
paramNameDur,
|
|
||||||
strconv.FormatFloat(float64(m.Duration)/float64(time.Millisecond), 'f', -1, 64),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// All remaining extra params
|
|
||||||
for k, v := range m.Extra {
|
|
||||||
parts = append(parts, headerEncodeParam(k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoString is needed for fmt.GoStringer so %v works on pointer value.
|
|
||||||
func (m *Metric) GoString() string {
|
|
||||||
if m == nil {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("*%#v", *m)
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package servertiming
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/felixge/httpsnoop"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MiddlewareOpts are options for the Middleware.
|
|
||||||
type MiddlewareOpts struct {
|
|
||||||
// Nothing currently, reserved for the future.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware wraps an http.Handler and provides a *Header in the request
|
|
||||||
// context that can be used to set Server-Timing headers. The *Header can be
|
|
||||||
// extracted from the context using FromContext.
|
|
||||||
//
|
|
||||||
// The options supplied to this can be nil to use defaults.
|
|
||||||
//
|
|
||||||
// The Server-Timing header will be written when the status is written
|
|
||||||
// only if there are non-empty number of metrics.
|
|
||||||
//
|
|
||||||
// To control when Server-Timing is sent, the easiest approach is to wrap
|
|
||||||
// this middleware and only call it if the request should send server timings.
|
|
||||||
// For examples, see the README.
|
|
||||||
func Middleware(next http.Handler, _ *MiddlewareOpts) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var (
|
|
||||||
// Create the Server-Timing headers struct
|
|
||||||
h Header
|
|
||||||
// Remember if the timing header were added to the response headers
|
|
||||||
headerWritten bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// This places the *Header value into the request context. This
|
|
||||||
// can be extracted again with FromContext.
|
|
||||||
r = r.WithContext(NewContext(r.Context(), &h))
|
|
||||||
|
|
||||||
// Get the header map. This is a reference and shouldn't change.
|
|
||||||
headers := w.Header()
|
|
||||||
|
|
||||||
// Hook the response writer we pass upstream so we can modify headers
|
|
||||||
// before they write them to the wire, but after we know what status
|
|
||||||
// they are writing.
|
|
||||||
hooks := httpsnoop.Hooks{
|
|
||||||
WriteHeader: func(original httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
|
||||||
// Return a function with same signature as
|
|
||||||
// http.ResponseWriter.WriteHeader to be called in it's place
|
|
||||||
return func(code int) {
|
|
||||||
// Write the headers
|
|
||||||
writeHeader(headers, &h)
|
|
||||||
|
|
||||||
// Remember that headers were written
|
|
||||||
headerWritten = true
|
|
||||||
|
|
||||||
// Call the original WriteHeader function
|
|
||||||
original(code)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
w = httpsnoop.Wrap(w, hooks)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
// In case that next did not called WriteHeader function, add timing header to the response headers
|
|
||||||
if !headerWritten {
|
|
||||||
writeHeader(headers, &h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeHeader(headers http.Header, h *Header) {
|
|
||||||
// Grab the lock just in case there is any ongoing concurrency that
|
|
||||||
// still has a reference and may be modifying the value.
|
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
|
|
||||||
// If there are no metrics set, do nothing
|
|
||||||
if len(h.Metrics) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
headers.Set(HeaderKey, h.String())
|
|
||||||
}
|
|
|
@ -8,5 +8,3 @@ require (
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.2.2
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !ppc64le,!arm64,!s390x arm64,!go1.11 gccgo appengine
|
// +build !arm64,!s390x,!ppc64le arm64,!go1.11 gccgo appengine
|
||||||
|
|
||||||
package chacha20
|
package chacha20
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue