go-pkgz/auth and go-pkgz/email modules updated, bump deps
This commit is contained in:
+21
-21
@@ -11,13 +11,13 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-pkgz/auth v1.22.0
|
||||
github.com/go-pkgz/auth v1.22.1-0.20231219172726-eb8671b06587
|
||||
github.com/go-pkgz/jrpc v0.3.0
|
||||
github.com/go-pkgz/lcw v1.0.2
|
||||
github.com/go-pkgz/lgr v0.11.0
|
||||
github.com/go-pkgz/notify v0.2.0
|
||||
github.com/go-pkgz/lgr v0.11.1
|
||||
github.com/go-pkgz/notify v1.1.0
|
||||
github.com/go-pkgz/repeater v1.1.3
|
||||
github.com/go-pkgz/rest v1.17.0
|
||||
github.com/go-pkgz/rest v1.18.2
|
||||
github.com/go-pkgz/syncs v1.3.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
@@ -25,20 +25,20 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jessevdk/go-flags v1.5.0
|
||||
github.com/kyokomi/emoji/v2 v2.2.12
|
||||
github.com/microcosm-cc/bluemonday v1.0.25
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.etcd.io/bbolt v1.3.8
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/net v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.23.0 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
@@ -49,30 +49,30 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 // indirect
|
||||
github.com/go-pkgz/email v0.4.1 // indirect
|
||||
github.com/go-pkgz/email v0.5.0 // indirect
|
||||
github.com/go-pkgz/expirable-cache v1.0.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/slack-go/slack v0.12.2 // indirect
|
||||
github.com/slack-go/slack v0.12.3 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.1 // indirect
|
||||
golang.org/x/oauth2 v0.11.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+43
-43
@@ -1,6 +1,6 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||
@@ -62,10 +62,10 @@ github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
||||
github.com/go-pkgz/auth v1.22.0 h1:txa42KFDR+xl36ok6/geaUzyVnLFks/GwFYjCoHjC4Y=
|
||||
github.com/go-pkgz/auth v1.22.0/go.mod h1:knVxcFhXlZ9AUvB1nlOR0kWghVBcgfFH1ucPW9CjCmE=
|
||||
github.com/go-pkgz/email v0.4.1 h1:2vtP2gibsSzqhz6eD5DklSp11m657XEVf17fuXaxMvk=
|
||||
github.com/go-pkgz/email v0.4.1/go.mod h1:BdxglsQnymzhfdbnncEE72a6DrucZHy6I+42LK2jLEc=
|
||||
github.com/go-pkgz/auth v1.22.1-0.20231219172726-eb8671b06587 h1:1cihW3uZm41ff3s6jBULVkUo/Y6FgUzXeNx4mUNpMsw=
|
||||
github.com/go-pkgz/auth v1.22.1-0.20231219172726-eb8671b06587/go.mod h1:Vb92mtxdQIreeitkn/A5CoGyrEIrAmEsxD6RqJ2luy4=
|
||||
github.com/go-pkgz/email v0.5.0 h1:fdtMDGJ8NwyBACLR0LYHaCIK/OeUwZHMhH7Q0+oty9U=
|
||||
github.com/go-pkgz/email v0.5.0/go.mod h1:BdxglsQnymzhfdbnncEE72a6DrucZHy6I+42LK2jLEc=
|
||||
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
github.com/go-pkgz/expirable-cache v1.0.0 h1:ns5+1hjY8hntGv8bPaQd9Gr7Jyo+Uw5SLyII40aQdtA=
|
||||
github.com/go-pkgz/expirable-cache v1.0.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
@@ -73,15 +73,15 @@ github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||
github.com/go-pkgz/lcw v1.0.2 h1:68T8xRkVWSo1KIbMfFXBY1xbFF3XWl7YD+ub3jkUlNE=
|
||||
github.com/go-pkgz/lcw v1.0.2/go.mod h1:CPJJzunpmGToOtD0Ga82TV152eL69sYEIIPcy9fbxlU=
|
||||
github.com/go-pkgz/lgr v0.11.0 h1:9XH5o+vj09L0sRWEswIGK1lJ6g07xVB4/Z28RV9Z+qM=
|
||||
github.com/go-pkgz/lgr v0.11.0/go.mod h1:4rdRmMSs4yGFjnUg0rSDbKx21LmFNZoH4y8OLl3qDnU=
|
||||
github.com/go-pkgz/notify v0.2.0 h1:mxHjcLc3goT+k1qnBPJ06PpNuVUDcu21Xy+6hEo4IaU=
|
||||
github.com/go-pkgz/notify v0.2.0/go.mod h1:uB4bcohkZB+P8zT279KQv5aXqQEkeMXI3Ey0Y3r0SQk=
|
||||
github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM=
|
||||
github.com/go-pkgz/lgr v0.11.1/go.mod h1:tgDF4RXQnBfIgJqjgkv0yOeTQ3F1yewWIZkpUhHnAkU=
|
||||
github.com/go-pkgz/notify v1.1.0 h1:xdMKoY7W5t9lewGzn61yM6Z6oWafPiPS1WtAMO81p2k=
|
||||
github.com/go-pkgz/notify v1.1.0/go.mod h1:TbqcFbamfJZlhlqXkGz87rCb7sJ0uGm+b7EcMCMz918=
|
||||
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
|
||||
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.17.0 h1:LoBI/lDBMuqwWhOOkc6thM9NnwJO+K9nWvCOjZ7BAgE=
|
||||
github.com/go-pkgz/rest v1.17.0/go.mod h1:HHlLOt02NJc2sgffXBF6hYVMcRo4Gz3vjg43zTzN7VM=
|
||||
github.com/go-pkgz/rest v1.18.2 h1:eJYj1qlLJvTx86R4o+XmlKHOAGAX42WeG9PZrJud/e0=
|
||||
github.com/go-pkgz/rest v1.18.2/go.mod h1:Po+W6zQzpMPP6XDGLdAN2aW7UKk1IyrLSb48Lp1N3oQ=
|
||||
github.com/go-pkgz/syncs v1.3.2 h1:gmioASlJNy3gNosPlgvWOM2QP0Hdjzn2u+/sUShgd8E=
|
||||
github.com/go-pkgz/syncs v1.3.2/go.mod h1:qjgzpp7OpuhDf7BWsW/FHCu9DLjE32NPy6/vXAXT/Cw=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
@@ -114,7 +114,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
@@ -122,13 +122,13 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -147,8 +147,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -158,8 +158,8 @@ github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit
|
||||
github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
@@ -194,8 +194,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ=
|
||||
github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/slack-go/slack v0.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88=
|
||||
github.com/slack-go/slack v0.12.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -260,10 +260,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
|
||||
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -273,10 +273,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -298,19 +298,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
|
||||
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -335,8 +335,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -349,8 +349,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -363,8 +363,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "1.23.0"
|
||||
const Version = "1.23.3"
|
||||
|
||||
+13
-12
@@ -214,18 +214,19 @@ The provider acts like any other, i.e. will be registered as `/auth/email/login`
|
||||
|
||||
For email notify provider, please use `github.com/go-pkgz/auth/provider/sender` package:
|
||||
```go
|
||||
sndr := sender.NewEmailClient(sender.EmailParams{
|
||||
Host: "email.hostname",
|
||||
Port: 567,
|
||||
SMTPUserName: "username",
|
||||
SMTPPassword: "pass",
|
||||
StartTLS: true,
|
||||
From: "notify@email.hostname",
|
||||
Subject: "subject",
|
||||
ContentType: "text/html",
|
||||
Charset: "UTF-8",
|
||||
}, log.Default())
|
||||
authenticator.AddVerifProvider("email", "template goes here", sndr)
|
||||
sndr := sender.NewEmailClient(sender.EmailParams{
|
||||
Host: "email.hostname",
|
||||
Port: 567,
|
||||
SMTPUserName: "username",
|
||||
SMTPPassword: "pass",
|
||||
StartTLS: true,
|
||||
InsecureSkipVerify: false,
|
||||
From: "notify@email.hostname",
|
||||
Subject: "subject",
|
||||
ContentType: "text/html",
|
||||
Charset: "UTF-8",
|
||||
}, log.Default())
|
||||
authenticator.AddVerifProvider("email", "template goes here", sndr)
|
||||
```
|
||||
|
||||
See [that documentation](https://github.com/go-pkgz/email#options) for full options list.
|
||||
|
||||
+30
-10
@@ -222,19 +222,22 @@ func (s *Service) Middleware() middleware.Authenticator {
|
||||
return s.authMiddleware
|
||||
}
|
||||
|
||||
// AddProvider adds provider for given name
|
||||
func (s *Service) AddProvider(name, cid, csecret string) {
|
||||
|
||||
// AddProviderWithUserAttributes adds provider with user attributes mapping
|
||||
func (s *Service) AddProviderWithUserAttributes(name, cid, csecret string, userAttributes provider.UserAttributes) {
|
||||
p := provider.Params{
|
||||
URL: s.opts.URL,
|
||||
JwtService: s.jwtService,
|
||||
Issuer: s.issuer,
|
||||
AvatarSaver: s.avatarProxy,
|
||||
Cid: cid,
|
||||
Csecret: csecret,
|
||||
L: s.logger,
|
||||
URL: s.opts.URL,
|
||||
JwtService: s.jwtService,
|
||||
Issuer: s.issuer,
|
||||
AvatarSaver: s.avatarProxy,
|
||||
Cid: cid,
|
||||
Csecret: csecret,
|
||||
L: s.logger,
|
||||
UserAttributes: userAttributes,
|
||||
}
|
||||
s.addProvider(name, p)
|
||||
}
|
||||
|
||||
func (s *Service) addProvider(name string, p provider.Params) {
|
||||
switch strings.ToLower(name) {
|
||||
case "github":
|
||||
s.providers = append(s.providers, provider.NewService(provider.NewGithub(p)))
|
||||
@@ -261,6 +264,23 @@ func (s *Service) AddProvider(name, cid, csecret string) {
|
||||
s.authMiddleware.Providers = s.providers
|
||||
}
|
||||
|
||||
// AddProvider adds provider for given name
|
||||
func (s *Service) AddProvider(name, cid, csecret string) {
|
||||
|
||||
p := provider.Params{
|
||||
URL: s.opts.URL,
|
||||
JwtService: s.jwtService,
|
||||
Issuer: s.issuer,
|
||||
AvatarSaver: s.avatarProxy,
|
||||
Cid: cid,
|
||||
Csecret: csecret,
|
||||
L: s.logger,
|
||||
UserAttributes: map[string]string{},
|
||||
}
|
||||
|
||||
s.addProvider(name, p)
|
||||
}
|
||||
|
||||
// AddDevProvider with a custom host and port
|
||||
func (s *Service) AddDevProvider(host string, port int) {
|
||||
p := provider.Params{
|
||||
|
||||
+7
-6
@@ -34,12 +34,13 @@ type Oauth2Handler struct {
|
||||
// Params to make initialized and ready to use provider
|
||||
type Params struct {
|
||||
logger.L
|
||||
URL string
|
||||
JwtService TokenService
|
||||
Cid string
|
||||
Csecret string
|
||||
Issuer string
|
||||
AvatarSaver AvatarSaver
|
||||
URL string
|
||||
JwtService TokenService
|
||||
Cid string
|
||||
Csecret string
|
||||
Issuer string
|
||||
AvatarSaver AvatarSaver
|
||||
UserAttributes UserAttributes
|
||||
|
||||
Port int // relevant for providers supporting port customization, for example dev oauth2
|
||||
Host string // relevant for providers supporting host customization, for example dev oauth2
|
||||
|
||||
+27
-2
@@ -17,6 +17,9 @@ import (
|
||||
"golang.org/x/oauth2/yandex"
|
||||
)
|
||||
|
||||
// UserAttributes is the type that will be used to map user data from provider to token.User
|
||||
type UserAttributes map[string]string
|
||||
|
||||
// NewGoogle makes google oauth2 provider
|
||||
func NewGoogle(p Params) Oauth2Handler {
|
||||
return initOauth2Handler(p, Oauth2Handler{
|
||||
@@ -34,6 +37,9 @@ func NewGoogle(p Params) Oauth2Handler {
|
||||
if userInfo.Name == "" {
|
||||
userInfo.Name = "noname_" + userInfo.ID[8:12]
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -56,6 +62,9 @@ func NewGithub(p Params) Oauth2Handler {
|
||||
if userInfo.Name == "" {
|
||||
userInfo.Name = data.Value("login")
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -93,6 +102,9 @@ func NewFacebook(p Params) Oauth2Handler {
|
||||
if err := json.Unmarshal(bdata, &uinfoJSON); err == nil {
|
||||
userInfo.Picture = uinfoJSON.Picture.Data.URL
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -121,6 +133,9 @@ func NewYandex(p Params) Oauth2Handler {
|
||||
if data.Value("default_avatar_id") != "" {
|
||||
userInfo.Picture = fmt.Sprintf("https://avatars.yandex.net/get-yapic/%s/islands-200", data.Value("default_avatar_id"))
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -143,6 +158,9 @@ func NewTwitter(p Params) Oauth1Handler {
|
||||
if userInfo.Name == "" {
|
||||
userInfo.Name = data.Value("name")
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -164,7 +182,9 @@ func NewBattlenet(p Params) Oauth2Handler {
|
||||
ID: "battlenet_" + token.HashID(sha1.New(), data.Value("id")),
|
||||
Name: data.Value("battletag"),
|
||||
}
|
||||
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -185,6 +205,9 @@ func NewMicrosoft(p Params) Oauth2Handler {
|
||||
Name: data.Value("displayName"),
|
||||
Picture: "https://graph.microsoft.com/beta/me/photo/$value",
|
||||
}
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
@@ -235,7 +258,9 @@ func NewPatreon(p Params) Oauth2Handler {
|
||||
userInfo.SetPaidSub(true)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range p.UserAttributes {
|
||||
userInfo.SetStrAttr(v, data.Value(k))
|
||||
}
|
||||
return userInfo
|
||||
},
|
||||
})
|
||||
|
||||
+12
-7
@@ -24,13 +24,14 @@ type EmailParams struct {
|
||||
Subject string // Email subject
|
||||
ContentType string // Content type
|
||||
|
||||
TLS bool // TLS auth
|
||||
StartTLS bool // StartTLS auth
|
||||
Charset string // Character set
|
||||
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
|
||||
SMTPUserName string // username
|
||||
SMTPPassword string // password
|
||||
TimeOut time.Duration // TCP connection timeout
|
||||
TLS bool // TLS auth
|
||||
StartTLS bool // StartTLS auth
|
||||
InsecureSkipVerify bool // Skip certificate verification
|
||||
Charset string // Character set
|
||||
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
|
||||
SMTPUserName string // username
|
||||
SMTPPassword string // password
|
||||
TimeOut time.Duration // TCP connection timeout
|
||||
}
|
||||
|
||||
// NewEmailClient creates email client
|
||||
@@ -69,6 +70,10 @@ func NewEmailClient(emailParams EmailParams, l logger.L) *Email {
|
||||
opts = append(opts, email.STARTTLS(true))
|
||||
}
|
||||
|
||||
if emailParams.InsecureSkipVerify {
|
||||
opts = append(opts, email.InsecureSkipVerify(true))
|
||||
}
|
||||
|
||||
sender := email.NewSender(emailParams.Host, opts...)
|
||||
|
||||
return &Email{EmailParams: emailParams, L: l, sender: sender}
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ err := client.Send("<html>some content, foo bar</html>",
|
||||
- `Port`: SMTP port (default: 25)
|
||||
- `TLS`: Use TLS SMTP (default: false)
|
||||
- `STARTTLS`: Use STARTTLS (default: false)
|
||||
- `InsecureSkipVerify`: skip certificate verification (default: false)
|
||||
- `Auth(user, password)`: Username and password for SMTP authentication (default: empty, no authentication)
|
||||
- `LoginAuth`: Use [LOGIN mechanism](https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt) instead of PLAIN mechanism for SMTP authentication, e.g. this is relevant for Office 365 and Outlook.com
|
||||
- `ContentType`: Content type for the email (default: "text/plain")
|
||||
|
||||
+33
-30
@@ -26,19 +26,20 @@ import (
|
||||
|
||||
// Sender implements email sender
|
||||
type Sender struct {
|
||||
smtpClient SMTPClient
|
||||
logger Logger
|
||||
host string // SMTP host
|
||||
port int // SMTP port
|
||||
contentType string // Content type, optional. Will trigger MIME and Content-Type headers
|
||||
tls bool // TLS auth
|
||||
starttls bool // StartTLS
|
||||
smtpUserName string // username
|
||||
smtpPassword string // password
|
||||
authMethod authMethod // auth method
|
||||
timeOut time.Duration
|
||||
contentCharset string
|
||||
timeNow func() time.Time
|
||||
smtpClient SMTPClient
|
||||
logger Logger
|
||||
host string // SMTP host
|
||||
port int // SMTP port
|
||||
contentType string // Content type, optional. Will trigger MIME and Content-Type headers
|
||||
tls bool // TLS auth
|
||||
starttls bool // StartTLS
|
||||
insecureSkipVerify bool // Insecure Skip Verify
|
||||
smtpUserName string // username
|
||||
smtpPassword string // password
|
||||
authMethod authMethod // auth method
|
||||
timeOut time.Duration
|
||||
contentCharset string
|
||||
timeNow func() time.Time
|
||||
}
|
||||
|
||||
// Params contains all user-defined parameters to send emails
|
||||
@@ -70,26 +71,27 @@ type SMTPClient interface {
|
||||
// NewSender creates email client with prepared smtp
|
||||
func NewSender(smtpHost string, options ...Option) *Sender {
|
||||
res := Sender{
|
||||
smtpClient: nil,
|
||||
logger: nopLogger{},
|
||||
host: smtpHost,
|
||||
port: 25,
|
||||
contentType: `text/plain`,
|
||||
tls: false,
|
||||
smtpUserName: "",
|
||||
smtpPassword: "",
|
||||
authMethod: authMethodPlain,
|
||||
contentCharset: "UTF-8",
|
||||
timeOut: time.Second * 30,
|
||||
timeNow: time.Now,
|
||||
smtpClient: nil,
|
||||
logger: nopLogger{},
|
||||
host: smtpHost,
|
||||
port: 25,
|
||||
contentType: `text/plain`,
|
||||
tls: false,
|
||||
insecureSkipVerify: false,
|
||||
smtpUserName: "",
|
||||
smtpPassword: "",
|
||||
authMethod: authMethodPlain,
|
||||
contentCharset: "UTF-8",
|
||||
timeOut: time.Second * 30,
|
||||
timeNow: time.Now,
|
||||
}
|
||||
for _, opt := range options {
|
||||
opt(&res)
|
||||
}
|
||||
|
||||
res.logger.Logf("[INFO] new email sender created with host: %s:%d, tls: %v, username: %q, timeout: %v, "+
|
||||
res.logger.Logf("[INFO] new email sender created with host: %s:%d, tls: %v, insecureSkipVerify: %v, username: %q, timeout: %v, "+
|
||||
"content type: %q, charset: %q", smtpHost,
|
||||
res.port, res.tls, res.smtpUserName, res.timeOut, res.contentType, res.contentCharset)
|
||||
res.port, res.tls, res.insecureSkipVerify, res.smtpUserName, res.timeOut, res.contentType, res.contentCharset)
|
||||
return &res
|
||||
}
|
||||
|
||||
@@ -164,14 +166,15 @@ func (em *Sender) Send(text string, params Params) error {
|
||||
}
|
||||
|
||||
func (em *Sender) String() string {
|
||||
return fmt.Sprintf("smtp://%s:%d, auth:%v, tls:%v, starttls:%v, timeout:%v, content-type:%q, charset:%q",
|
||||
em.host, em.port, em.smtpUserName != "", em.tls, em.starttls, em.timeOut, em.contentType, em.contentCharset)
|
||||
return fmt.Sprintf("smtp://%s:%d, auth:%v, tls:%v, starttls:%v, insecureSkipVerify:%v, timeout:%v, content-type:%q, charset:%q",
|
||||
em.host, em.port, em.smtpUserName != "", em.tls, em.starttls, em.insecureSkipVerify, em.timeOut, em.contentType, em.contentCharset)
|
||||
}
|
||||
|
||||
func (em *Sender) client() (c *smtp.Client, err error) {
|
||||
srvAddress := fmt.Sprintf("%s:%d", em.host, em.port)
|
||||
// #nosec G402
|
||||
tlsConf := &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
InsecureSkipVerify: em.insecureSkipVerify, // #nosec G402
|
||||
ServerName: em.host,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
+7
@@ -54,6 +54,13 @@ func STARTTLS(enabled bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// InsecureSkipVerify skips certificate verification
|
||||
func InsecureSkipVerify(enabled bool) Option {
|
||||
return func(s *Sender) {
|
||||
s.insecureSkipVerify = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Auth sets smtp username and password
|
||||
func Auth(smtpUserName, smtpPasswd string) Option {
|
||||
return func(s *Sender) {
|
||||
|
||||
+7
-1
@@ -1,6 +1,9 @@
|
||||
package lgr
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Option func type
|
||||
type Option func(l *Logger)
|
||||
@@ -74,6 +77,9 @@ func Msec(l *Logger) {
|
||||
func Secret(vals ...string) Option {
|
||||
return func(l *Logger) {
|
||||
for _, v := range vals {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
continue // skip empty secrets
|
||||
}
|
||||
l.secrets = append(l.secrets, []byte(v))
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -36,17 +36,13 @@ linters:
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
|
||||
+12
-8
@@ -33,9 +33,12 @@ func main() {
|
||||
notify.NewWebhook(notify.WebhookParams{}),
|
||||
notify.NewEmail(notify.SMTPParams{}),
|
||||
notify.NewSlack("token"),
|
||||
notify.NewTelegram(notify.TelegramParams{token: "token"}),
|
||||
}
|
||||
err := notify.Send(context.Background(), notifiers, "https://example.com/webhook", "Hello, world!")
|
||||
tg, err := notify.NewTelegram(notify.TelegramParams{Token: "token"})
|
||||
if err == nil {
|
||||
notifiers = append(notifiers, tg)
|
||||
}
|
||||
err = notify.Send(context.Background(), notifiers, "https://example.com/webhook", "Hello, world!")
|
||||
if err != nil {
|
||||
fmt.Printf("Sent message error: %s", err))
|
||||
}
|
||||
@@ -100,18 +103,19 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/notify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tg := notify.NewTelegram(notify.TelegramParams{
|
||||
Token: "token", // required
|
||||
Timeout: time.Second * 10, // default is 5 seconds
|
||||
SuccessMsg: // optional, for auth, set by default
|
||||
ErrorMsg: // optional, for auth, unset by default
|
||||
tg, err := notify.NewTelegram(notify.TelegramParams{
|
||||
Token: "token", // required
|
||||
Timeout: time.Second * 10, // default is 5 seconds
|
||||
SuccessMsg: "Success", // optional, for auth, set by default
|
||||
ErrorMsg: "Error", // optional, for auth, unset by default
|
||||
})
|
||||
err := tg.Send(context.Background(), "telegram:-1001480738202", "Hello, World!")
|
||||
err = tg.Send(context.Background(), "telegram:-1001480738202", "Hello, World!")
|
||||
if err != nil {
|
||||
log.Fatalf("problem sending message using telegram, %v", err)
|
||||
}
|
||||
|
||||
+5
@@ -17,6 +17,7 @@ type SMTPParams struct {
|
||||
Port int // SMTP port
|
||||
TLS bool // TLS auth
|
||||
StartTLS bool // StartTLS auth
|
||||
InsecureSkipVerify bool // skip certificate verification
|
||||
ContentType string // Content type
|
||||
Charset string // Character set
|
||||
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
|
||||
@@ -67,6 +68,10 @@ func NewEmail(smtpParams SMTPParams) *Email {
|
||||
opts = append(opts, email.STARTTLS(true))
|
||||
}
|
||||
|
||||
if smtpParams.InsecureSkipVerify {
|
||||
opts = append(opts, email.InsecureSkipVerify(true))
|
||||
}
|
||||
|
||||
sender := email.NewSender(smtpParams.Host, opts...)
|
||||
|
||||
return &Email{sender: sender, SMTPParams: smtpParams}
|
||||
|
||||
+3
@@ -57,6 +57,9 @@ run:
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- text: 'Deferring unsafe method "Close" on type "io.ReadCloser"'
|
||||
linters:
|
||||
- gosec
|
||||
- text: "should have a package comment, unless it's in another file for this package"
|
||||
linters:
|
||||
- golint
|
||||
|
||||
+26
-15
@@ -97,16 +97,22 @@ example: `019/03/05 17:26:12.976 [INFO] GET - /api/v1/find?site=remark - 8e228e9
|
||||
### Recoverer middleware
|
||||
|
||||
Recoverer is a middleware that recovers from panics, logs the panic (and a backtrace),
|
||||
and returns an HTTP 500 (Internal Server Error) status if possible.
|
||||
and returns an HTTP 500 (Internal Server Error) status if possible.
|
||||
It prevents server crashes in case of panic in one of the controllers.
|
||||
|
||||
### OnlyFrom middleware
|
||||
|
||||
OnlyFrom middleware allows access for limited list of source IPs.
|
||||
Such IPs can be defined as complete ip (like 192.168.1.12), prefix (129.168.) or CIDR (192.168.0.0/16)
|
||||
OnlyFrom middleware allows access from a limited list of source IPs.
|
||||
Such IPs can be defined as complete ip (like 192.168.1.12), prefix (129.168.) or CIDR (192.168.0.0/16).
|
||||
The middleware will respond with `StatusForbidden` (403) if the request comes from a different IP.
|
||||
It supports both IPv4 and IPv6 and checks the usual headers like `X-Forwarded-For` and `X-Real-IP` and the remote address.
|
||||
|
||||
_Note: headers should be trusted and set by a proxy, otherwise it is possible to spoof them._
|
||||
|
||||
### Metrics middleware
|
||||
|
||||
Metrics middleware responds to GET /metrics with list of [expvar](https://golang.org/pkg/expvar/). Optionally allows restricting list of source ips.
|
||||
Metrics middleware responds to GET /metrics with list of [expvar](https://golang.org/pkg/expvar/).
|
||||
Optionally allows a restricted list of source ips.
|
||||
|
||||
### BlackWords middleware
|
||||
|
||||
@@ -118,9 +124,9 @@ SizeLimit middleware checks if body size is above the limit and returns `StatusR
|
||||
|
||||
### Trace middleware
|
||||
|
||||
It looks for `X-Request-ID` header and makes it as a random id
|
||||
(if not found), then populates it to the result's header
|
||||
and to the request's context.
|
||||
The `Trace` middleware is designed to add request tracing functionality. It looks for the `X-Request-ID` header in
|
||||
the incoming HTTP request. If not found, a random ID is generated. This trace ID is then set in the response headers
|
||||
and added to the request's context.
|
||||
|
||||
### Deprecation middleware
|
||||
|
||||
@@ -134,7 +140,9 @@ BasicAuth middleware requires basic auth and matches user & passwd with client-p
|
||||
|
||||
### Rewrite middleware
|
||||
|
||||
Rewrites requests with from->to rule. Supports regex (like nginx) and prevents multiple rewrites. For example `Rewrite("^/sites/(.*)/settings/$", "/sites/settings/$1")` will change request's URL from `/sites/id1/settings/` to `/sites/settings/id1`
|
||||
The `Rewrite` middleware is designed to rewrite the URL path based on a given rule, similar to how URL rewriting is done in nginx. It supports regular expressions for pattern matching and prevents multiple rewrites.
|
||||
|
||||
For example, `Rewrite("^/sites/(.*)/settings/$", "/sites/settings/$1")` will change request's URL from `/sites/id1/settings/` to `/sites/settings/id1`
|
||||
|
||||
### NoCache middleware
|
||||
|
||||
@@ -154,14 +162,14 @@ RealIP is a middleware that sets a http.Request's RemoteAddr to the results of p
|
||||
|
||||
### Maybe middleware
|
||||
|
||||
Maybe middleware will allow you to change the flow of the middleware stack execution depending on return
|
||||
value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if
|
||||
a request does not satisfy the maybeFn logic.
|
||||
Maybe middleware allows changing the flow of the middleware stack execution depending on the return
|
||||
value of maybeFn(request). This is useful, for example, to skip a middleware handler if a request does not satisfy the maybeFn logic.
|
||||
|
||||
### Reject middleware
|
||||
|
||||
Reject is a middleware that rejects requests with a given status code and message based on a user-defined function.
|
||||
This is useful for example if you'd like to reject requests to a particular resource based on a request header, or want to implement a conditional request handler based on service parameters.
|
||||
This is useful, for example, to reject requests to a particular resource based on a request header,
|
||||
or to implement a conditional request handler based on service parameters.
|
||||
|
||||
example with chi router:
|
||||
|
||||
@@ -177,9 +185,12 @@ example with chi router:
|
||||
|
||||
### Benchmarks middleware
|
||||
|
||||
Benchmarks middleware allows to measure the time of request handling, number of request per second and report aggregated metrics. This middleware keeps track of the request in the memory and keep up to 900 points (15 minutes, data-point per second).
|
||||
Benchmarks middleware allows measuring the time of request handling, number of requests per second and report aggregated metrics.
|
||||
This middleware keeps track of the request in the memory and keep up to 900 points (15 minutes, data-point per second).
|
||||
|
||||
In order to retrieve the data user should call `Stats(d duration)` method. duration is the time window for which the benchmark data should be returned. It can be any duration from 1s to 15m. Note: all the time data is in microseconds.
|
||||
To retrieve the data user should call `Stats(d duration)` method.
|
||||
The `duration` is the time window for which the benchmark data should be returned.
|
||||
It can be any duration from 1s to 15m. Note: all the time data is in microseconds.
|
||||
|
||||
example with chi router:
|
||||
|
||||
@@ -217,7 +228,7 @@ example with chi router:
|
||||
|
||||
## Profiler
|
||||
|
||||
Profiler is a convenient subrouter used for mounting net/http/pprof, i.e.
|
||||
Profiler is a convenient sub-router used for mounting net/http/pprof, i.e.
|
||||
|
||||
```go
|
||||
func MyService() http.Handler {
|
||||
|
||||
+26
@@ -45,3 +45,29 @@ func IsAuthorized(ctx context.Context) bool {
|
||||
v := ctx.Value(contextKey(baContextKey))
|
||||
return v != nil && v.(bool)
|
||||
}
|
||||
|
||||
// BasicAuthWithPrompt middleware requires basic auth and matches user & passwd with client-provided values
|
||||
// If the user is not authorized, it will prompt for basic auth
|
||||
func BasicAuthWithPrompt(user, passwd string) func(http.Handler) http.Handler {
|
||||
checkFn := func(reqUser, reqPasswd string) bool {
|
||||
matchUser := subtle.ConstantTimeCompare([]byte(user), []byte(reqUser))
|
||||
matchPass := subtle.ConstantTimeCompare([]byte(passwd), []byte(reqPasswd))
|
||||
return matchUser == 1 && matchPass == 1
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// extract basic auth from request
|
||||
u, p, ok := r.BasicAuth()
|
||||
if ok && checkFn(u, p) {
|
||||
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey(baContextKey), true)))
|
||||
return
|
||||
}
|
||||
// not authorized, prompt for basic auth
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -9,12 +9,10 @@ import (
|
||||
|
||||
// Metrics responds to GET /metrics with list of expvar
|
||||
func Metrics(onlyIps ...string) func(http.Handler) http.Handler {
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.HasSuffix(strings.ToLower(r.URL.Path), "/metrics") {
|
||||
if matched, ip := matchSourceIP(r, onlyIps); !matched {
|
||||
if matched, ip, err := matchSourceIP(r, onlyIps); !matched || err != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
RenderJSON(w, JSON{"error": fmt.Sprintf("ip %s rejected", ip)})
|
||||
return
|
||||
|
||||
+22
-21
@@ -5,54 +5,55 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pkgz/rest/realip"
|
||||
)
|
||||
|
||||
// OnlyFrom middleware allows access for limited list of source IPs.
|
||||
// Such IPs can be defined as complete ip (like 192.168.1.12), prefix (129.168.) or CIDR (192.168.0.0/16)
|
||||
func OnlyFrom(onlyIps ...string) func(http.Handler) http.Handler {
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
matched, ip := matchSourceIP(r, onlyIps)
|
||||
if len(onlyIps) == 0 {
|
||||
// no restrictions if no ips defined
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
matched, ip, err := matchSourceIP(r, onlyIps)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
RenderJSON(w, JSON{"error": fmt.Sprintf("can't get realip: %s", err)})
|
||||
return
|
||||
}
|
||||
if matched {
|
||||
// matched ip - allow
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
RenderJSON(w, JSON{"error": fmt.Sprintf("ip %s rejected", ip)})
|
||||
RenderJSON(w, JSON{"error": fmt.Sprintf("ip %q rejected", ip)})
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// matchSourceIP returns true if request's ip matches any of ips
|
||||
func matchSourceIP(r *http.Request, ips []string) (result bool, match string) {
|
||||
|
||||
// try X-Real-IP first then fail back to X-Forwarded-For and finally to RemoteAddr
|
||||
ip := r.Header.Get("X-Real-IP")
|
||||
if ip == "" {
|
||||
ip = strings.Split(r.Header.Get("X-Forwarded-For"), ", ")[0]
|
||||
func matchSourceIP(r *http.Request, ips []string) (result bool, match string, err error) {
|
||||
ip, err := realip.Get(r)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("can't get realip: %w", err) // we can't get ip, so no match
|
||||
}
|
||||
if ip == "" {
|
||||
ip = r.Header.Get("RemoteAddr")
|
||||
}
|
||||
if ip == "" {
|
||||
ip = strings.Split(r.RemoteAddr, ":")[0]
|
||||
}
|
||||
|
||||
// check for ip prefix or CIDR
|
||||
for _, exclIP := range ips {
|
||||
if _, cidrnet, err := net.ParseCIDR(exclIP); err == nil {
|
||||
if cidrnet.Contains(net.ParseIP(ip)) {
|
||||
return true, ip
|
||||
return true, ip, nil
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(ip, exclIP) {
|
||||
return true, ip
|
||||
return true, ip, nil
|
||||
}
|
||||
}
|
||||
return false, ip
|
||||
return false, ip, nil
|
||||
}
|
||||
|
||||
+8
-8
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
// Profiler is a convenient subrouter used for mounting net/http/pprof. ie.
|
||||
//
|
||||
// func MyService() http.Handler {
|
||||
// r := chi.NewRouter()
|
||||
// // ..middlewares
|
||||
// r.Mount("/debug", middleware.Profiler())
|
||||
// // ..routes
|
||||
// return r
|
||||
// }
|
||||
// func MyService() http.Handler {
|
||||
// r := chi.NewRouter()
|
||||
// // ..middlewares
|
||||
// r.Mount("/debug", middleware.Profiler())
|
||||
// // ..routes
|
||||
// return r
|
||||
// }
|
||||
func Profiler(onlyIps ...string) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/pprof/", pprof.Index)
|
||||
@@ -33,7 +33,7 @@ func Profiler(onlyIps ...string) http.Handler {
|
||||
}
|
||||
|
||||
// expVars copied from stdlib expvar.go as is not public.
|
||||
func expVars(w http.ResponseWriter, r *http.Request) {
|
||||
func expVars(w http.ResponseWriter, _ *http.Request) {
|
||||
first := true
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
|
||||
+23
-22
@@ -21,18 +21,23 @@ var privateRanges = []ipRange{
|
||||
{start: net.ParseIP("192.0.0.0"), end: net.ParseIP("192.0.0.255")},
|
||||
{start: net.ParseIP("192.168.0.0"), end: net.ParseIP("192.168.255.255")},
|
||||
{start: net.ParseIP("198.18.0.0"), end: net.ParseIP("198.19.255.255")},
|
||||
{start: net.ParseIP("::1"), end: net.ParseIP("::1")},
|
||||
{start: net.ParseIP("fc00::"), end: net.ParseIP("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
|
||||
{start: net.ParseIP("fe80::"), end: net.ParseIP("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
|
||||
}
|
||||
|
||||
// Get returns real ip from the given request
|
||||
// Prioritize public IPs over private IPs
|
||||
func Get(r *http.Request) (string, error) {
|
||||
|
||||
var firstIP string
|
||||
for _, h := range []string{"X-Forwarded-For", "X-Real-Ip"} {
|
||||
addresses := strings.Split(r.Header.Get(h), ",")
|
||||
// march from right to left until we get a public address
|
||||
// that will be the address right before our proxy.
|
||||
for i := len(addresses) - 1; i >= 0; i-- {
|
||||
ip := strings.TrimSpace(addresses[i])
|
||||
realIP := net.ParseIP(ip)
|
||||
if firstIP == "" && realIP != nil {
|
||||
firstIP = ip
|
||||
}
|
||||
if !realIP.IsGlobalUnicast() || isPrivateSubnet(realIP) {
|
||||
continue
|
||||
}
|
||||
@@ -40,12 +45,10 @@ func Get(r *http.Request) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// X-Forwarded-For header set but parsing failed above
|
||||
if r.Header.Get("X-Forwarded-For") != "" {
|
||||
return "", fmt.Errorf("no valid ip found")
|
||||
if firstIP != "" {
|
||||
return firstIP, nil
|
||||
}
|
||||
|
||||
// get IP from RemoteAddr
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't parse ip %q: %w", r.RemoteAddr, err)
|
||||
@@ -57,23 +60,21 @@ func Get(r *http.Request) (string, error) {
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// inRange - check to see if a given ip address is within a range given
|
||||
func inRange(r ipRange, ipAddress net.IP) bool {
|
||||
// strcmp type byte comparison
|
||||
if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isPrivateSubnet - check to see if this ip is in a private subnet
|
||||
func isPrivateSubnet(ipAddress net.IP) bool {
|
||||
if ipCheck := ipAddress.To4(); ipCheck != nil {
|
||||
for _, r := range privateRanges {
|
||||
// check if this ip is in a private range
|
||||
if inRange(r, ipAddress) {
|
||||
return true
|
||||
}
|
||||
|
||||
// inRange - check to see if a given ip address is within a range given
|
||||
inRange := func(r ipRange, ipAddress net.IP) bool {
|
||||
// ensure the IPs are in the same format for comparison
|
||||
ipAddress = ipAddress.To16()
|
||||
r.start = r.start.To16()
|
||||
r.end = r.end.To16()
|
||||
return bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) <= 0
|
||||
}
|
||||
|
||||
for _, r := range privateRanges {
|
||||
if inRange(r, ipAddress) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
+24
-23
@@ -1,27 +1,28 @@
|
||||
Copyright (c) 2013, Gorilla web toolkit
|
||||
All rights reserved.
|
||||
Copyright (c) 2023 The Gorilla 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:
|
||||
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 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Neither the name of the {organization} 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 HOLDER 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.
|
||||
|
||||
+5
-1
@@ -191,7 +191,11 @@ func init() {
|
||||
// New returns a new CSS scanner for the given input.
|
||||
func New(input string) *Scanner {
|
||||
// Normalize newlines.
|
||||
// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
|
||||
input = strings.Replace(input, "\r\n", "\n", -1)
|
||||
input = strings.Replace(input, "\r", "\n", -1)
|
||||
input = strings.Replace(input, "\f", "\n", -1)
|
||||
input = strings.Replace(input, "\u0000", "\ufffd", -1)
|
||||
return &Scanner{
|
||||
input: input,
|
||||
row: 1,
|
||||
@@ -232,7 +236,7 @@ func (s *Scanner) Next() *Token {
|
||||
// shortcut before testing multiple regexps.
|
||||
input := s.input[s.pos:]
|
||||
switch input[0] {
|
||||
case '\t', '\n', '\f', '\r', ' ':
|
||||
case '\t', '\n', ' ':
|
||||
// Whitespace.
|
||||
return s.emitToken(TokenS, matchers[TokenS].FindString(input))
|
||||
case '.':
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
; https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
eclint_indent_style = unset
|
||||
+1
-25
@@ -1,25 +1 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
coverage.coverprofile
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
run:
|
||||
skip-dirs:
|
||||
- examples/*.go
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
# This is the official list of Gorilla WebSocket authors for copyright
|
||||
# purposes.
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Google LLC (https://opensource.google.com/)
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
||||
+22
-17
@@ -1,22 +1,27 @@
|
||||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
Copyright (c) 2023 The Gorilla 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:
|
||||
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 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.
|
||||
|
||||
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.
|
||||
|
||||
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 HOLDER 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
|
||||
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.
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
|
||||
GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
GO_SEC=$(shell which gosec 2> /dev/null || echo '')
|
||||
GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
|
||||
GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
|
||||
GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint:
|
||||
$(if $(GO_LINT), ,go install $(GO_LINT_URI))
|
||||
@echo "##### Running golangci-lint"
|
||||
golangci-lint run -v
|
||||
|
||||
.PHONY: gosec
|
||||
gosec:
|
||||
$(if $(GO_SEC), ,go install $(GO_SEC_URI))
|
||||
@echo "##### Running gosec"
|
||||
gosec -exclude-dir examples ./...
|
||||
|
||||
.PHONY: govulncheck
|
||||
govulncheck:
|
||||
$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
|
||||
@echo "##### Running govulncheck"
|
||||
govulncheck ./...
|
||||
|
||||
.PHONY: verify
|
||||
verify: golangci-lint gosec govulncheck
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "##### Running tests"
|
||||
go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...
|
||||
+8
-11
@@ -1,17 +1,14 @@
|
||||
# Gorilla WebSocket
|
||||
# gorilla/websocket
|
||||
|
||||
[](https://godoc.org/github.com/gorilla/websocket)
|
||||
[](https://circleci.com/gh/gorilla/websocket)
|
||||

|
||||
[](https://codecov.io/github/gorilla/websocket)
|
||||
[](https://godoc.org/github.com/gorilla/websocket)
|
||||
[](https://sourcegraph.com/github.com/gorilla/websocket?badge)
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
⚠️ **[The Gorilla WebSocket Package is looking for a new maintainer](https://github.com/gorilla/websocket/issues/370)**
|
||||
|
||||
---
|
||||
|
||||
### Documentation
|
||||
|
||||
@@ -20,6 +17,7 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||
* [Write buffer pool example](https://github.com/gorilla/websocket/tree/master/examples/bufferpool)
|
||||
|
||||
### Status
|
||||
|
||||
@@ -36,4 +34,3 @@ package API is stable.
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
|
||||
+33
-11
@@ -9,14 +9,18 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||
@@ -224,6 +228,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
k == "Connection" ||
|
||||
k == "Sec-Websocket-Key" ||
|
||||
k == "Sec-Websocket-Version" ||
|
||||
//#nosec G101 (CWE-798): Potential HTTP request smuggling via parameter pollution
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
@@ -289,7 +294,9 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
}
|
||||
err = c.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
if err := c.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
@@ -303,7 +310,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
return nil, nil, err
|
||||
}
|
||||
if proxyURL != nil {
|
||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||
dialer, err := proxy.FromURL(proxyURL, netDialerFunc(netDial))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -318,18 +325,20 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", hostPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if trace != nil && trace.GotConn != nil {
|
||||
trace.GotConn(httptrace.GotConnInfo{
|
||||
Conn: netConn,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if netConn != nil {
|
||||
netConn.Close()
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -370,6 +379,17 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
if d.TLSClientConfig != nil {
|
||||
for _, proto := range d.TLSClientConfig.NextProtos {
|
||||
if proto != "http/1.1" {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"websocket: protocol %q was given but is not supported;"+
|
||||
"sharing tls.Config with net/http Transport can cause this error: %w",
|
||||
proto, err,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -388,7 +408,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
// debugging.
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := io.ReadFull(resp.Body, buf)
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:n]))
|
||||
return nil, resp, ErrBadHandshake
|
||||
}
|
||||
|
||||
@@ -406,17 +426,19 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
break
|
||||
}
|
||||
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
|
||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||
|
||||
netConn.SetDeadline(time.Time{})
|
||||
if err := netConn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
return &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
}
|
||||
return cfg.Clone()
|
||||
}
|
||||
|
||||
+7
-2
@@ -8,6 +8,7 @@ import (
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -33,7 +34,9 @@ func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||
"\x01\x00\x00\xff\xff"
|
||||
|
||||
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||
if err := fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &flateReadWrapper{fr}
|
||||
}
|
||||
|
||||
@@ -132,7 +135,9 @@ func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||
// Preemptively place the reader back in the pool. This helps with
|
||||
// scenarios where the application does not call NextReader() soon after
|
||||
// this final read.
|
||||
r.Close()
|
||||
if err := r.Close(); err != nil {
|
||||
log.Printf("websocket: flateReadWrapper.Close() returned error: %v", err)
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
+60
-23
@@ -6,11 +6,11 @@ package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -181,13 +181,20 @@ var (
|
||||
errInvalidControlFrame = errors.New("websocket: invalid control frame")
|
||||
)
|
||||
|
||||
// maskRand is an io.Reader for generating mask bytes. The reader is initialized
|
||||
// to crypto/rand Reader. Tests swap the reader to a math/rand reader for
|
||||
// reproducible results.
|
||||
var maskRand = rand.Reader
|
||||
|
||||
// newMaskKey returns a new 32 bit value for masking client frames.
|
||||
func newMaskKey() [4]byte {
|
||||
n := rand.Uint32()
|
||||
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
|
||||
var k [4]byte
|
||||
_, _ = io.ReadFull(maskRand, k[:])
|
||||
return k
|
||||
}
|
||||
|
||||
func hideTempErr(err error) error {
|
||||
if e, ok := err.(net.Error); ok && e.Temporary() {
|
||||
if e, ok := err.(net.Error); ok {
|
||||
err = &netError{msg: e.Error(), timeout: e.Timeout()}
|
||||
}
|
||||
return err
|
||||
@@ -372,7 +379,9 @@ func (c *Conn) read(n int) ([]byte, error) {
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
if _, err := c.br.Discard(len(p)); err != nil {
|
||||
return p, err
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
@@ -387,7 +396,9 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error
|
||||
return err
|
||||
}
|
||||
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
if err := c.conn.SetWriteDeadline(deadline); err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
if len(buf1) == 0 {
|
||||
_, err = c.conn.Write(buf0)
|
||||
} else {
|
||||
@@ -397,7 +408,7 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
if frameType == CloseMessage {
|
||||
c.writeFatal(ErrCloseSent)
|
||||
_ = c.writeFatal(ErrCloseSent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -438,7 +449,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
|
||||
d := 1000 * time.Hour
|
||||
if !deadline.IsZero() {
|
||||
d = deadline.Sub(time.Now())
|
||||
d = time.Until(deadline)
|
||||
if d < 0 {
|
||||
return errWriteTimeout
|
||||
}
|
||||
@@ -460,13 +471,15 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
return err
|
||||
}
|
||||
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
if err := c.conn.SetWriteDeadline(deadline); err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
_, err = c.conn.Write(buf)
|
||||
if err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
if messageType == CloseMessage {
|
||||
c.writeFatal(ErrCloseSent)
|
||||
_ = c.writeFatal(ErrCloseSent)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -477,7 +490,9 @@ func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
// probably better to return an error in this situation, but we cannot
|
||||
// change this without breaking existing applications.
|
||||
if c.writer != nil {
|
||||
c.writer.Close()
|
||||
if err := c.writer.Close(); err != nil {
|
||||
log.Printf("websocket: discarding writer close error: %v", err)
|
||||
}
|
||||
c.writer = nil
|
||||
}
|
||||
|
||||
@@ -630,7 +645,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
}
|
||||
|
||||
if final {
|
||||
w.endMessage(errWriteClosed)
|
||||
_ = w.endMessage(errWriteClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -795,7 +810,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
// 1. Skip remainder of previous frame.
|
||||
|
||||
if c.readRemaining > 0 {
|
||||
if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil {
|
||||
if _, err := io.CopyN(io.Discard, c.br, c.readRemaining); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
}
|
||||
@@ -817,7 +832,9 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
rsv2 := p[0]&rsv2Bit != 0
|
||||
rsv3 := p[0]&rsv3Bit != 0
|
||||
mask := p[1]&maskBit != 0
|
||||
c.setReadRemaining(int64(p[1] & 0x7f))
|
||||
if err := c.setReadRemaining(int64(p[1] & 0x7f)); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
|
||||
c.readDecompress = false
|
||||
if rsv1 {
|
||||
@@ -922,7 +939,9 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
}
|
||||
|
||||
if c.readLimit > 0 && c.readLength > c.readLimit {
|
||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
|
||||
if err := c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
return noFrame, ErrReadLimit
|
||||
}
|
||||
|
||||
@@ -934,7 +953,9 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
var payload []byte
|
||||
if c.readRemaining > 0 {
|
||||
payload, err = c.read(int(c.readRemaining))
|
||||
c.setReadRemaining(0)
|
||||
if err := c.setReadRemaining(0); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
@@ -981,7 +1002,9 @@ func (c *Conn) handleProtocolError(message string) error {
|
||||
if len(data) > maxControlFramePayloadSize {
|
||||
data = data[:maxControlFramePayloadSize]
|
||||
}
|
||||
c.WriteControl(CloseMessage, data, time.Now().Add(writeWait))
|
||||
if err := c.WriteControl(CloseMessage, data, time.Now().Add(writeWait)); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("websocket: " + message)
|
||||
}
|
||||
|
||||
@@ -998,7 +1021,9 @@ func (c *Conn) handleProtocolError(message string) error {
|
||||
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
||||
// Close previous reader, only relevant for decompression.
|
||||
if c.reader != nil {
|
||||
c.reader.Close()
|
||||
if err := c.reader.Close(); err != nil {
|
||||
log.Printf("websocket: discarding reader close error: %v", err)
|
||||
}
|
||||
c.reader = nil
|
||||
}
|
||||
|
||||
@@ -1054,7 +1079,9 @@ func (r *messageReader) Read(b []byte) (int, error) {
|
||||
}
|
||||
rem := c.readRemaining
|
||||
rem -= int64(n)
|
||||
c.setReadRemaining(rem)
|
||||
if err := c.setReadRemaining(rem); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if c.readRemaining > 0 && c.readErr == io.EOF {
|
||||
c.readErr = errUnexpectedEOF
|
||||
}
|
||||
@@ -1094,7 +1121,7 @@ func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
|
||||
if err != nil {
|
||||
return messageType, nil, err
|
||||
}
|
||||
p, err = ioutil.ReadAll(r)
|
||||
p, err = io.ReadAll(r)
|
||||
return messageType, p, err
|
||||
}
|
||||
|
||||
@@ -1136,7 +1163,9 @@ func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||
if h == nil {
|
||||
h = func(code int, text string) error {
|
||||
message := FormatCloseMessage(code, "")
|
||||
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||
if err := c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1161,7 +1190,7 @@ func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||
err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
|
||||
if err == ErrCloseSent {
|
||||
return nil
|
||||
} else if e, ok := err.(net.Error); ok && e.Temporary() {
|
||||
} else if _, ok := err.(net.Error); ok {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -1189,8 +1218,16 @@ func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||
c.handlePong = h
|
||||
}
|
||||
|
||||
// NetConn returns the underlying connection that is wrapped by c.
|
||||
// Note that writing to or reading from this connection directly will corrupt the
|
||||
// WebSocket connection.
|
||||
func (c *Conn) NetConn() net.Conn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// UnderlyingConn returns the internal net.Conn. This can be used to further
|
||||
// modifications to connection specific flags.
|
||||
// Deprecated: Use the NetConn method.
|
||||
func (c *Conn) UnderlyingConn() net.Conn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
+4
@@ -9,6 +9,7 @@ package websocket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// #nosec G103 -- (CWE-242) Has been audited
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
@@ -22,6 +23,7 @@ func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
}
|
||||
|
||||
// Mask one byte at a time to word boundary.
|
||||
//#nosec G103 -- (CWE-242) Has been audited
|
||||
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||
n = wordSize - n
|
||||
for i := range b[:n] {
|
||||
@@ -36,11 +38,13 @@ func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
for i := range k {
|
||||
k[i] = key[(pos+i)&3]
|
||||
}
|
||||
//#nosec G103 -- (CWE-242) Has been audited
|
||||
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||
|
||||
// Mask one word at a time.
|
||||
n := (len(b) / wordSize) * wordSize
|
||||
for i := 0; i < n; i += wordSize {
|
||||
//#nosec G103 -- (CWE-242) Has been audited
|
||||
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||
}
|
||||
|
||||
|
||||
+13
-4
@@ -8,10 +8,13 @@ import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
type netDialerFunc func(network, addr string) (net.Conn, error)
|
||||
@@ -21,7 +24,7 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||
proxy.RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy.Dialer) (proxy.Dialer, error) {
|
||||
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
||||
})
|
||||
}
|
||||
@@ -55,7 +58,9 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
if err := connectReq.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Printf("httpProxyDialer: failed to close connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -64,12 +69,16 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
|
||||
br := bufio.NewReader(conn)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Printf("httpProxyDialer: failed to close connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Printf("httpProxyDialer: failed to close connection: %v", err)
|
||||
}
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, errors.New(f[1])
|
||||
}
|
||||
|
||||
+33
-9
@@ -8,6 +8,7 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -154,8 +155,8 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
}
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
|
||||
if !isValidChallengeKey(challengeKey) {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header must be Base64 encoded value of 16-byte in length")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
@@ -183,7 +184,9 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
}
|
||||
|
||||
if brw.Reader.Buffered() > 0 {
|
||||
netConn.Close()
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||
}
|
||||
|
||||
@@ -248,17 +251,34 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
p = append(p, "\r\n"...)
|
||||
|
||||
// Clear deadlines set by HTTP server.
|
||||
netConn.SetDeadline(time.Time{})
|
||||
if err := netConn.SetDeadline(time.Time{}); err != nil {
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
|
||||
if err := netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)); err != nil {
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err = netConn.Write(p); err != nil {
|
||||
netConn.Close()
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Time{})
|
||||
if err := netConn.SetWriteDeadline(time.Time{}); err != nil {
|
||||
if err := netConn.Close(); err != nil {
|
||||
log.Printf("websocket: failed to close network connection: %v", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
@@ -356,8 +376,12 @@ func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
|
||||
// bufio.Writer's underlying writer.
|
||||
var wh writeHook
|
||||
bw.Reset(&wh)
|
||||
bw.WriteByte(0)
|
||||
bw.Flush()
|
||||
if err := bw.WriteByte(0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
log.Printf("websocket: bufioWriterBuffer: Flush: %v", err)
|
||||
}
|
||||
|
||||
bw.Reset(originalWriter)
|
||||
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
func doHandshake(ctx context.Context, 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
|
||||
}
|
||||
+17
-2
@@ -6,7 +6,7 @@ package websocket
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha1" //#nosec G505 -- (CWE-327) https://datatracker.ietf.org/doc/html/rfc6455#page-54
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
|
||||
func computeAcceptKey(challengeKey string) string {
|
||||
h := sha1.New()
|
||||
h := sha1.New() //#nosec G401 -- (CWE-326) https://datatracker.ietf.org/doc/html/rfc6455#page-54
|
||||
h.Write([]byte(challengeKey))
|
||||
h.Write(keyGUID)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
@@ -281,3 +281,18 @@ headers:
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isValidChallengeKey checks if the argument meets RFC6455 specification.
|
||||
func isValidChallengeKey(s string) bool {
|
||||
// From RFC6455:
|
||||
//
|
||||
// A |Sec-WebSocket-Key| header field with a base64-encoded (see
|
||||
// Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
|
||||
// length.
|
||||
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||
return err == nil && len(decoded) == 16
|
||||
}
|
||||
|
||||
-473
@@ -1,473 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
+3
-17
@@ -3,7 +3,7 @@
|
||||
before:
|
||||
hooks:
|
||||
- ./gen.sh
|
||||
- go install mvdan.cc/garble@v0.9.3
|
||||
- go install mvdan.cc/garble@v0.10.1
|
||||
|
||||
builds:
|
||||
-
|
||||
@@ -92,16 +92,7 @@ builds:
|
||||
archives:
|
||||
-
|
||||
id: s2-binaries
|
||||
name_template: "s2-{{ .Os }}_{{ .Arch }}_{{ .Version }}"
|
||||
replacements:
|
||||
aix: AIX
|
||||
darwin: OSX
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
freebsd: FreeBSD
|
||||
netbsd: NetBSD
|
||||
name_template: "s2-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
@@ -125,7 +116,7 @@ changelog:
|
||||
|
||||
nfpms:
|
||||
-
|
||||
file_name_template: "s2_package_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
file_name_template: "s2_package__{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
vendor: Klaus Post
|
||||
homepage: https://github.com/klauspost/compress
|
||||
maintainer: Klaus Post <klauspost@gmail.com>
|
||||
@@ -134,8 +125,3 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
freebsd: FreeBSD
|
||||
amd64: x86_64
|
||||
|
||||
+27
@@ -16,6 +16,26 @@ This package provides various compression algorithms.
|
||||
|
||||
# changelog
|
||||
|
||||
* Oct 22nd, 2023 - [v1.17.2](https://github.com/klauspost/compress/releases/tag/v1.17.2)
|
||||
* zstd: Fix rare *CORRUPTION* output in "best" mode. See https://github.com/klauspost/compress/pull/876
|
||||
|
||||
* Oct 14th, 2023 - [v1.17.1](https://github.com/klauspost/compress/releases/tag/v1.17.1)
|
||||
* s2: Fix S2 "best" dictionary wrong encoding by @klauspost in https://github.com/klauspost/compress/pull/871
|
||||
* flate: Reduce allocations in decompressor and minor code improvements by @fakefloordiv in https://github.com/klauspost/compress/pull/869
|
||||
* s2: Fix EstimateBlockSize on 6&7 length input by @klauspost in https://github.com/klauspost/compress/pull/867
|
||||
|
||||
* Sept 19th, 2023 - [v1.17.0](https://github.com/klauspost/compress/releases/tag/v1.17.0)
|
||||
* Add experimental dictionary builder https://github.com/klauspost/compress/pull/853
|
||||
* Add xerial snappy read/writer https://github.com/klauspost/compress/pull/838
|
||||
* flate: Add limited window compression https://github.com/klauspost/compress/pull/843
|
||||
* s2: Do 2 overlapping match checks https://github.com/klauspost/compress/pull/839
|
||||
* flate: Add amd64 assembly matchlen https://github.com/klauspost/compress/pull/837
|
||||
* gzip: Copy bufio.Reader on Reset by @thatguystone in https://github.com/klauspost/compress/pull/860
|
||||
|
||||
* July 1st, 2023 - [v1.16.7](https://github.com/klauspost/compress/releases/tag/v1.16.7)
|
||||
* zstd: Fix default level first dictionary encode https://github.com/klauspost/compress/pull/829
|
||||
* s2: add GetBufferCapacity() method by @GiedriusS in https://github.com/klauspost/compress/pull/832
|
||||
|
||||
* June 13, 2023 - [v1.16.6](https://github.com/klauspost/compress/releases/tag/v1.16.6)
|
||||
* zstd: correctly ignore WithEncoderPadding(1) by @ianlancetaylor in https://github.com/klauspost/compress/pull/806
|
||||
* zstd: Add amd64 match length assembly https://github.com/klauspost/compress/pull/824
|
||||
@@ -50,6 +70,9 @@ This package provides various compression algorithms.
|
||||
* s2: Support io.ReaderAt in ReadSeeker. https://github.com/klauspost/compress/pull/747
|
||||
* s2c/s2sx: Use concurrent decoding. https://github.com/klauspost/compress/pull/746
|
||||
|
||||
<details>
|
||||
<summary>See changes to v1.15.x</summary>
|
||||
|
||||
* Jan 21st, 2023 (v1.15.15)
|
||||
* deflate: Improve level 7-9 by @klauspost in https://github.com/klauspost/compress/pull/739
|
||||
* zstd: Add delta encoding support by @greatroar in https://github.com/klauspost/compress/pull/728
|
||||
@@ -176,6 +199,8 @@ Stream decompression is now faster on asynchronous, since the goroutine allocati
|
||||
|
||||
While the release has been extensively tested, it is recommended to testing when upgrading.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>See changes to v1.14.x</summary>
|
||||
|
||||
@@ -636,6 +661,8 @@ Here are other packages of good quality and pure Go (no cgo wrappers or autoconv
|
||||
* [github.com/dsnet/compress](https://github.com/dsnet/compress) - brotli decompression, bzip2 writer.
|
||||
* [github.com/ronanh/intcomp](https://github.com/ronanh/intcomp) - Integer compression.
|
||||
* [github.com/spenczar/fpc](https://github.com/spenczar/fpc) - Float compression.
|
||||
* [github.com/minio/zipindex](https://github.com/minio/zipindex) - External ZIP directory index.
|
||||
* [github.com/ybirader/pzip](https://github.com/ybirader/pzip) - Fast concurrent zip archiver and extractor.
|
||||
|
||||
# license
|
||||
|
||||
|
||||
+1
-2
@@ -152,12 +152,11 @@ func (b *bitWriter) flushAlign() {
|
||||
|
||||
// close will write the alignment bit and write the final byte(s)
|
||||
// to the output.
|
||||
func (b *bitWriter) close() error {
|
||||
func (b *bitWriter) close() {
|
||||
// End mark
|
||||
b.addBits16Clean(1, 1)
|
||||
// flush until next byte.
|
||||
b.flushAlign()
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset and continue writing by appending to out.
|
||||
|
||||
+3
-2
@@ -199,7 +199,8 @@ func (s *Scratch) compress(src []byte) error {
|
||||
c2.flush(s.actualTableLog)
|
||||
c1.flush(s.actualTableLog)
|
||||
|
||||
return s.bw.close()
|
||||
s.bw.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeCount will write the normalized histogram count to header.
|
||||
@@ -211,7 +212,7 @@ func (s *Scratch) writeCount() error {
|
||||
previous0 bool
|
||||
charnum uint16
|
||||
|
||||
maxHeaderSize = ((int(s.symbolLen) * int(tableLog)) >> 3) + 3
|
||||
maxHeaderSize = ((int(s.symbolLen)*int(tableLog) + 4 + 2) >> 3) + 3
|
||||
|
||||
// Write Table Size
|
||||
bitStream = uint32(tableLog - minTablelog)
|
||||
|
||||
+1
-2
@@ -94,10 +94,9 @@ func (b *bitWriter) flushAlign() {
|
||||
|
||||
// close will write the alignment bit and write the final byte(s)
|
||||
// to the output.
|
||||
func (b *bitWriter) close() error {
|
||||
func (b *bitWriter) close() {
|
||||
// End mark
|
||||
b.addBits16Clean(1, 1)
|
||||
// flush until next byte.
|
||||
b.flushAlign()
|
||||
return nil
|
||||
}
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2018 Klaus Post. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Based on work Copyright (c) 2013, Yann Collet, released under BSD License.
|
||||
|
||||
package huff0
|
||||
|
||||
// byteReader provides a byte reader that reads
|
||||
// little endian values from a byte stream.
|
||||
// The input stream is manually advanced.
|
||||
// The reader performs no bounds checks.
|
||||
type byteReader struct {
|
||||
b []byte
|
||||
off int
|
||||
}
|
||||
|
||||
// init will initialize the reader and set the input.
|
||||
func (b *byteReader) init(in []byte) {
|
||||
b.b = in
|
||||
b.off = 0
|
||||
}
|
||||
|
||||
// Int32 returns a little endian int32 starting at current offset.
|
||||
func (b byteReader) Int32() int32 {
|
||||
v3 := int32(b.b[b.off+3])
|
||||
v2 := int32(b.b[b.off+2])
|
||||
v1 := int32(b.b[b.off+1])
|
||||
v0 := int32(b.b[b.off])
|
||||
return (v3 << 24) | (v2 << 16) | (v1 << 8) | v0
|
||||
}
|
||||
|
||||
// Uint32 returns a little endian uint32 starting at current offset.
|
||||
func (b byteReader) Uint32() uint32 {
|
||||
v3 := uint32(b.b[b.off+3])
|
||||
v2 := uint32(b.b[b.off+2])
|
||||
v1 := uint32(b.b[b.off+1])
|
||||
v0 := uint32(b.b[b.off])
|
||||
return (v3 << 24) | (v2 << 16) | (v1 << 8) | v0
|
||||
}
|
||||
|
||||
// remain will return the number of bytes remaining.
|
||||
func (b byteReader) remain() int {
|
||||
return len(b.b) - b.off
|
||||
}
|
||||
+9
-16
@@ -227,10 +227,10 @@ func EstimateSizes(in []byte, s *Scratch) (tableSz, dataSz, reuseSz int, err err
|
||||
}
|
||||
|
||||
func (s *Scratch) compress1X(src []byte) ([]byte, error) {
|
||||
return s.compress1xDo(s.Out, src)
|
||||
return s.compress1xDo(s.Out, src), nil
|
||||
}
|
||||
|
||||
func (s *Scratch) compress1xDo(dst, src []byte) ([]byte, error) {
|
||||
func (s *Scratch) compress1xDo(dst, src []byte) []byte {
|
||||
var bw = bitWriter{out: dst}
|
||||
|
||||
// N is length divisible by 4.
|
||||
@@ -260,8 +260,8 @@ func (s *Scratch) compress1xDo(dst, src []byte) ([]byte, error) {
|
||||
bw.encTwoSymbols(cTable, tmp[1], tmp[0])
|
||||
}
|
||||
}
|
||||
err := bw.close()
|
||||
return bw.out, err
|
||||
bw.close()
|
||||
return bw.out
|
||||
}
|
||||
|
||||
var sixZeros [6]byte
|
||||
@@ -283,12 +283,8 @@ func (s *Scratch) compress4X(src []byte) ([]byte, error) {
|
||||
}
|
||||
src = src[len(toDo):]
|
||||
|
||||
var err error
|
||||
idx := len(s.Out)
|
||||
s.Out, err = s.compress1xDo(s.Out, toDo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Out = s.compress1xDo(s.Out, toDo)
|
||||
if len(s.Out)-idx > math.MaxUint16 {
|
||||
// We cannot store the size in the jump table
|
||||
return nil, ErrIncompressible
|
||||
@@ -315,7 +311,6 @@ func (s *Scratch) compress4Xp(src []byte) ([]byte, error) {
|
||||
|
||||
segmentSize := (len(src) + 3) / 4
|
||||
var wg sync.WaitGroup
|
||||
var errs [4]error
|
||||
wg.Add(4)
|
||||
for i := 0; i < 4; i++ {
|
||||
toDo := src
|
||||
@@ -326,15 +321,12 @@ func (s *Scratch) compress4Xp(src []byte) ([]byte, error) {
|
||||
|
||||
// Separate goroutine for each block.
|
||||
go func(i int) {
|
||||
s.tmpOut[i], errs[i] = s.compress1xDo(s.tmpOut[i][:0], toDo)
|
||||
s.tmpOut[i] = s.compress1xDo(s.tmpOut[i][:0], toDo)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
for i := 0; i < 4; i++ {
|
||||
if errs[i] != nil {
|
||||
return nil, errs[i]
|
||||
}
|
||||
o := s.tmpOut[i]
|
||||
if len(o) > math.MaxUint16 {
|
||||
// We cannot store the size in the jump table
|
||||
@@ -358,6 +350,7 @@ func (s *Scratch) compress4Xp(src []byte) ([]byte, error) {
|
||||
// Does not update s.clearCount.
|
||||
func (s *Scratch) countSimple(in []byte) (max int, reuse bool) {
|
||||
reuse = true
|
||||
_ = s.count // Assert that s != nil to speed up the following loop.
|
||||
for _, v := range in {
|
||||
s.count[v]++
|
||||
}
|
||||
@@ -423,7 +416,7 @@ func (s *Scratch) validateTable(c cTable) bool {
|
||||
|
||||
// minTableLog provides the minimum logSize to safely represent a distribution.
|
||||
func (s *Scratch) minTableLog() uint8 {
|
||||
minBitsSrc := highBit32(uint32(s.br.remain())) + 1
|
||||
minBitsSrc := highBit32(uint32(s.srcLen)) + 1
|
||||
minBitsSymbols := highBit32(uint32(s.symbolLen-1)) + 2
|
||||
if minBitsSrc < minBitsSymbols {
|
||||
return uint8(minBitsSrc)
|
||||
@@ -435,7 +428,7 @@ func (s *Scratch) minTableLog() uint8 {
|
||||
func (s *Scratch) optimalTableLog() {
|
||||
tableLog := s.TableLog
|
||||
minBits := s.minTableLog()
|
||||
maxBitsSrc := uint8(highBit32(uint32(s.br.remain()-1))) - 1
|
||||
maxBitsSrc := uint8(highBit32(uint32(s.srcLen-1))) - 1
|
||||
if maxBitsSrc < tableLog {
|
||||
// Accuracy can be reduced
|
||||
tableLog = maxBitsSrc
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ type Scratch struct {
|
||||
// Decoders will return ErrMaxDecodedSizeExceeded is this limit is exceeded.
|
||||
MaxDecodedSize int
|
||||
|
||||
br byteReader
|
||||
srcLen int
|
||||
|
||||
// MaxSymbolValue will override the maximum symbol value of the next block.
|
||||
MaxSymbolValue uint8
|
||||
@@ -170,7 +170,7 @@ func (s *Scratch) prepare(in []byte) (*Scratch, error) {
|
||||
if s.fse == nil {
|
||||
s.fse = &fse.Scratch{}
|
||||
}
|
||||
s.br.init(in)
|
||||
s.srcLen = len(in)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ nyc-taxi-data-10M.csv gzkp 1 3325605752 922273214 13929 227.68
|
||||
|
||||
## Decompressor
|
||||
|
||||
Staus: STABLE - there may still be subtle bugs, but a wide variety of content has been tested.
|
||||
Status: STABLE - there may still be subtle bugs, but a wide variety of content has been tested.
|
||||
|
||||
This library is being continuously [fuzz-tested](https://github.com/klauspost/compress-fuzz),
|
||||
kindly supplied by [fuzzit.dev](https://fuzzit.dev/).
|
||||
|
||||
+15
-19
@@ -17,7 +17,6 @@ import (
|
||||
// for aligning the input.
|
||||
type bitReader struct {
|
||||
in []byte
|
||||
off uint // next byte to read is at in[off - 1]
|
||||
value uint64 // Maybe use [16]byte, but shifting is awkward.
|
||||
bitsRead uint8
|
||||
}
|
||||
@@ -28,7 +27,6 @@ func (b *bitReader) init(in []byte) error {
|
||||
return errors.New("corrupt stream: too short")
|
||||
}
|
||||
b.in = in
|
||||
b.off = uint(len(in))
|
||||
// The highest bit of the last byte indicates where to start
|
||||
v := in[len(in)-1]
|
||||
if v == 0 {
|
||||
@@ -69,21 +67,19 @@ func (b *bitReader) fillFast() {
|
||||
if b.bitsRead < 32 {
|
||||
return
|
||||
}
|
||||
// 2 bounds checks.
|
||||
v := b.in[b.off-4:]
|
||||
v = v[:4]
|
||||
v := b.in[len(b.in)-4:]
|
||||
b.in = b.in[:len(b.in)-4]
|
||||
low := (uint32(v[0])) | (uint32(v[1]) << 8) | (uint32(v[2]) << 16) | (uint32(v[3]) << 24)
|
||||
b.value = (b.value << 32) | uint64(low)
|
||||
b.bitsRead -= 32
|
||||
b.off -= 4
|
||||
}
|
||||
|
||||
// fillFastStart() assumes the bitreader is empty and there is at least 8 bytes to read.
|
||||
func (b *bitReader) fillFastStart() {
|
||||
// Do single re-slice to avoid bounds checks.
|
||||
b.value = binary.LittleEndian.Uint64(b.in[b.off-8:])
|
||||
v := b.in[len(b.in)-8:]
|
||||
b.in = b.in[:len(b.in)-8]
|
||||
b.value = binary.LittleEndian.Uint64(v)
|
||||
b.bitsRead = 0
|
||||
b.off -= 8
|
||||
}
|
||||
|
||||
// fill() will make sure at least 32 bits are available.
|
||||
@@ -91,25 +87,25 @@ func (b *bitReader) fill() {
|
||||
if b.bitsRead < 32 {
|
||||
return
|
||||
}
|
||||
if b.off >= 4 {
|
||||
v := b.in[b.off-4:]
|
||||
v = v[:4]
|
||||
if len(b.in) >= 4 {
|
||||
v := b.in[len(b.in)-4:]
|
||||
b.in = b.in[:len(b.in)-4]
|
||||
low := (uint32(v[0])) | (uint32(v[1]) << 8) | (uint32(v[2]) << 16) | (uint32(v[3]) << 24)
|
||||
b.value = (b.value << 32) | uint64(low)
|
||||
b.bitsRead -= 32
|
||||
b.off -= 4
|
||||
return
|
||||
}
|
||||
for b.off > 0 {
|
||||
b.value = (b.value << 8) | uint64(b.in[b.off-1])
|
||||
b.bitsRead -= 8
|
||||
b.off--
|
||||
|
||||
b.bitsRead -= uint8(8 * len(b.in))
|
||||
for len(b.in) > 0 {
|
||||
b.value = (b.value << 8) | uint64(b.in[len(b.in)-1])
|
||||
b.in = b.in[:len(b.in)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// finished returns true if all bits have been read from the bit stream.
|
||||
func (b *bitReader) finished() bool {
|
||||
return b.off == 0 && b.bitsRead >= 64
|
||||
return len(b.in) == 0 && b.bitsRead >= 64
|
||||
}
|
||||
|
||||
// overread returns true if more bits have been requested than is on the stream.
|
||||
@@ -119,7 +115,7 @@ func (b *bitReader) overread() bool {
|
||||
|
||||
// remain returns the number of bits remaining.
|
||||
func (b *bitReader) remain() uint {
|
||||
return b.off*8 + 64 - uint(b.bitsRead)
|
||||
return 8*uint(len(b.in)) + 64 - uint(b.bitsRead)
|
||||
}
|
||||
|
||||
// close the bitstream and returns an error if out-of-buffer reads occurred.
|
||||
|
||||
+1
-2
@@ -97,12 +97,11 @@ func (b *bitWriter) flushAlign() {
|
||||
|
||||
// close will write the alignment bit and write the final byte(s)
|
||||
// to the output.
|
||||
func (b *bitWriter) close() error {
|
||||
func (b *bitWriter) close() {
|
||||
// End mark
|
||||
b.addBits16Clean(1, 1)
|
||||
// flush until next byte.
|
||||
b.flushAlign()
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset and continue writing by appending to out.
|
||||
|
||||
+22
-7
@@ -361,14 +361,21 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
|
||||
if len(lits) >= 1024 {
|
||||
// Use 4 Streams.
|
||||
out, reUsed, err = huff0.Compress4X(lits, b.litEnc)
|
||||
} else if len(lits) > 32 {
|
||||
} else if len(lits) > 16 {
|
||||
// Use 1 stream
|
||||
single = true
|
||||
out, reUsed, err = huff0.Compress1X(lits, b.litEnc)
|
||||
} else {
|
||||
err = huff0.ErrIncompressible
|
||||
}
|
||||
|
||||
if err == nil && len(out)+5 > len(lits) {
|
||||
// If we are close, we may still be worse or equal to raw.
|
||||
var lh literalsHeader
|
||||
lh.setSizes(len(out), len(lits), single)
|
||||
if len(out)+lh.size() >= len(lits) {
|
||||
err = huff0.ErrIncompressible
|
||||
}
|
||||
}
|
||||
switch err {
|
||||
case huff0.ErrIncompressible:
|
||||
if debugEncoder {
|
||||
@@ -503,7 +510,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
|
||||
if len(b.literals) >= 1024 && !raw {
|
||||
// Use 4 Streams.
|
||||
out, reUsed, err = huff0.Compress4X(b.literals, b.litEnc)
|
||||
} else if len(b.literals) > 32 && !raw {
|
||||
} else if len(b.literals) > 16 && !raw {
|
||||
// Use 1 stream
|
||||
single = true
|
||||
out, reUsed, err = huff0.Compress1X(b.literals, b.litEnc)
|
||||
@@ -511,6 +518,17 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
|
||||
err = huff0.ErrIncompressible
|
||||
}
|
||||
|
||||
if err == nil && len(out)+5 > len(b.literals) {
|
||||
// If we are close, we may still be worse or equal to raw.
|
||||
var lh literalsHeader
|
||||
lh.setSize(len(b.literals))
|
||||
szRaw := lh.size()
|
||||
lh.setSizes(len(out), len(b.literals), single)
|
||||
szComp := lh.size()
|
||||
if len(out)+szComp >= len(b.literals)+szRaw {
|
||||
err = huff0.ErrIncompressible
|
||||
}
|
||||
}
|
||||
switch err {
|
||||
case huff0.ErrIncompressible:
|
||||
lh.setType(literalsBlockRaw)
|
||||
@@ -773,10 +791,7 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
|
||||
ml.flush(mlEnc.actualTableLog)
|
||||
of.flush(ofEnc.actualTableLog)
|
||||
ll.flush(llEnc.actualTableLog)
|
||||
err = wr.close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wr.close()
|
||||
b.output = wr.out
|
||||
|
||||
// Maybe even add a bigger margin.
|
||||
|
||||
+376
-3
@@ -1,10 +1,13 @@
|
||||
package zstd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/klauspost/compress/huff0"
|
||||
)
|
||||
@@ -14,9 +17,8 @@ type dict struct {
|
||||
|
||||
litEnc *huff0.Scratch
|
||||
llDec, ofDec, mlDec sequenceDec
|
||||
//llEnc, ofEnc, mlEnc []*fseEncoder
|
||||
offsets [3]int
|
||||
content []byte
|
||||
offsets [3]int
|
||||
content []byte
|
||||
}
|
||||
|
||||
const dictMagic = "\x37\xa4\x30\xec"
|
||||
@@ -159,3 +161,374 @@ func InspectDictionary(b []byte) (interface {
|
||||
d, err := loadDict(b)
|
||||
return d, err
|
||||
}
|
||||
|
||||
type BuildDictOptions struct {
|
||||
// Dictionary ID.
|
||||
ID uint32
|
||||
|
||||
// Content to use to create dictionary tables.
|
||||
Contents [][]byte
|
||||
|
||||
// History to use for all blocks.
|
||||
History []byte
|
||||
|
||||
// Offsets to use.
|
||||
Offsets [3]int
|
||||
|
||||
// CompatV155 will make the dictionary compatible with Zstd v1.5.5 and earlier.
|
||||
// See https://github.com/facebook/zstd/issues/3724
|
||||
CompatV155 bool
|
||||
|
||||
// Use the specified encoder level.
|
||||
// The dictionary will be built using the specified encoder level,
|
||||
// which will reflect speed and make the dictionary tailored for that level.
|
||||
// If not set SpeedBestCompression will be used.
|
||||
Level EncoderLevel
|
||||
|
||||
// DebugOut will write stats and other details here if set.
|
||||
DebugOut io.Writer
|
||||
}
|
||||
|
||||
func BuildDict(o BuildDictOptions) ([]byte, error) {
|
||||
initPredefined()
|
||||
hist := o.History
|
||||
contents := o.Contents
|
||||
debug := o.DebugOut != nil
|
||||
println := func(args ...interface{}) {
|
||||
if o.DebugOut != nil {
|
||||
fmt.Fprintln(o.DebugOut, args...)
|
||||
}
|
||||
}
|
||||
printf := func(s string, args ...interface{}) {
|
||||
if o.DebugOut != nil {
|
||||
fmt.Fprintf(o.DebugOut, s, args...)
|
||||
}
|
||||
}
|
||||
print := func(args ...interface{}) {
|
||||
if o.DebugOut != nil {
|
||||
fmt.Fprint(o.DebugOut, args...)
|
||||
}
|
||||
}
|
||||
|
||||
if int64(len(hist)) > dictMaxLength {
|
||||
return nil, fmt.Errorf("dictionary of size %d > %d", len(hist), int64(dictMaxLength))
|
||||
}
|
||||
if len(hist) < 8 {
|
||||
return nil, fmt.Errorf("dictionary of size %d < %d", len(hist), 8)
|
||||
}
|
||||
if len(contents) == 0 {
|
||||
return nil, errors.New("no content provided")
|
||||
}
|
||||
d := dict{
|
||||
id: o.ID,
|
||||
litEnc: nil,
|
||||
llDec: sequenceDec{},
|
||||
ofDec: sequenceDec{},
|
||||
mlDec: sequenceDec{},
|
||||
offsets: o.Offsets,
|
||||
content: hist,
|
||||
}
|
||||
block := blockEnc{lowMem: false}
|
||||
block.init()
|
||||
enc := encoder(&bestFastEncoder{fastBase: fastBase{maxMatchOff: int32(maxMatchLen), bufferReset: math.MaxInt32 - int32(maxMatchLen*2), lowMem: false}})
|
||||
if o.Level != 0 {
|
||||
eOpts := encoderOptions{
|
||||
level: o.Level,
|
||||
blockSize: maxMatchLen,
|
||||
windowSize: maxMatchLen,
|
||||
dict: &d,
|
||||
lowMem: false,
|
||||
}
|
||||
enc = eOpts.encoder()
|
||||
} else {
|
||||
o.Level = SpeedBestCompression
|
||||
}
|
||||
var (
|
||||
remain [256]int
|
||||
ll [256]int
|
||||
ml [256]int
|
||||
of [256]int
|
||||
)
|
||||
addValues := func(dst *[256]int, src []byte) {
|
||||
for _, v := range src {
|
||||
dst[v]++
|
||||
}
|
||||
}
|
||||
addHist := func(dst *[256]int, src *[256]uint32) {
|
||||
for i, v := range src {
|
||||
dst[i] += int(v)
|
||||
}
|
||||
}
|
||||
seqs := 0
|
||||
nUsed := 0
|
||||
litTotal := 0
|
||||
newOffsets := make(map[uint32]int, 1000)
|
||||
for _, b := range contents {
|
||||
block.reset(nil)
|
||||
if len(b) < 8 {
|
||||
continue
|
||||
}
|
||||
nUsed++
|
||||
enc.Reset(&d, true)
|
||||
enc.Encode(&block, b)
|
||||
addValues(&remain, block.literals)
|
||||
litTotal += len(block.literals)
|
||||
seqs += len(block.sequences)
|
||||
block.genCodes()
|
||||
addHist(&ll, block.coders.llEnc.Histogram())
|
||||
addHist(&ml, block.coders.mlEnc.Histogram())
|
||||
addHist(&of, block.coders.ofEnc.Histogram())
|
||||
for i, seq := range block.sequences {
|
||||
if i > 3 {
|
||||
break
|
||||
}
|
||||
offset := seq.offset
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
if offset > 3 {
|
||||
newOffsets[offset-3]++
|
||||
} else {
|
||||
newOffsets[uint32(o.Offsets[offset-1])]++
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find most used offsets.
|
||||
var sortedOffsets []uint32
|
||||
for k := range newOffsets {
|
||||
sortedOffsets = append(sortedOffsets, k)
|
||||
}
|
||||
sort.Slice(sortedOffsets, func(i, j int) bool {
|
||||
a, b := sortedOffsets[i], sortedOffsets[j]
|
||||
if a == b {
|
||||
// Prefer the longer offset
|
||||
return sortedOffsets[i] > sortedOffsets[j]
|
||||
}
|
||||
return newOffsets[sortedOffsets[i]] > newOffsets[sortedOffsets[j]]
|
||||
})
|
||||
if len(sortedOffsets) > 3 {
|
||||
if debug {
|
||||
print("Offsets:")
|
||||
for i, v := range sortedOffsets {
|
||||
if i > 20 {
|
||||
break
|
||||
}
|
||||
printf("[%d: %d],", v, newOffsets[v])
|
||||
}
|
||||
println("")
|
||||
}
|
||||
|
||||
sortedOffsets = sortedOffsets[:3]
|
||||
}
|
||||
for i, v := range sortedOffsets {
|
||||
o.Offsets[i] = int(v)
|
||||
}
|
||||
if debug {
|
||||
println("New repeat offsets", o.Offsets)
|
||||
}
|
||||
|
||||
if nUsed == 0 || seqs == 0 {
|
||||
return nil, fmt.Errorf("%d blocks, %d sequences found", nUsed, seqs)
|
||||
}
|
||||
if debug {
|
||||
println("Sequences:", seqs, "Blocks:", nUsed, "Literals:", litTotal)
|
||||
}
|
||||
if seqs/nUsed < 512 {
|
||||
// Use 512 as minimum.
|
||||
nUsed = seqs / 512
|
||||
}
|
||||
copyHist := func(dst *fseEncoder, src *[256]int) ([]byte, error) {
|
||||
hist := dst.Histogram()
|
||||
var maxSym uint8
|
||||
var maxCount int
|
||||
var fakeLength int
|
||||
for i, v := range src {
|
||||
if v > 0 {
|
||||
v = v / nUsed
|
||||
if v == 0 {
|
||||
v = 1
|
||||
}
|
||||
}
|
||||
if v > maxCount {
|
||||
maxCount = v
|
||||
}
|
||||
if v != 0 {
|
||||
maxSym = uint8(i)
|
||||
}
|
||||
fakeLength += v
|
||||
hist[i] = uint32(v)
|
||||
}
|
||||
dst.HistogramFinished(maxSym, maxCount)
|
||||
dst.reUsed = false
|
||||
dst.useRLE = false
|
||||
err := dst.normalizeCount(fakeLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if debug {
|
||||
println("RAW:", dst.count[:maxSym+1], "NORM:", dst.norm[:maxSym+1], "LEN:", fakeLength)
|
||||
}
|
||||
return dst.writeCount(nil)
|
||||
}
|
||||
if debug {
|
||||
print("Literal lengths: ")
|
||||
}
|
||||
llTable, err := copyHist(block.coders.llEnc, &ll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if debug {
|
||||
print("Match lengths: ")
|
||||
}
|
||||
mlTable, err := copyHist(block.coders.mlEnc, &ml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if debug {
|
||||
print("Offsets: ")
|
||||
}
|
||||
ofTable, err := copyHist(block.coders.ofEnc, &of)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Literal table
|
||||
avgSize := litTotal
|
||||
if avgSize > huff0.BlockSizeMax/2 {
|
||||
avgSize = huff0.BlockSizeMax / 2
|
||||
}
|
||||
huffBuff := make([]byte, 0, avgSize)
|
||||
// Target size
|
||||
div := litTotal / avgSize
|
||||
if div < 1 {
|
||||
div = 1
|
||||
}
|
||||
if debug {
|
||||
println("Huffman weights:")
|
||||
}
|
||||
for i, n := range remain[:] {
|
||||
if n > 0 {
|
||||
n = n / div
|
||||
// Allow all entries to be represented.
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
huffBuff = append(huffBuff, bytes.Repeat([]byte{byte(i)}, n)...)
|
||||
if debug {
|
||||
printf("[%d: %d], ", i, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.CompatV155 && remain[255]/div == 0 {
|
||||
huffBuff = append(huffBuff, 255)
|
||||
}
|
||||
scratch := &huff0.Scratch{TableLog: 11}
|
||||
for tries := 0; tries < 255; tries++ {
|
||||
scratch = &huff0.Scratch{TableLog: 11}
|
||||
_, _, err = huff0.Compress1X(huffBuff, scratch)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if debug {
|
||||
printf("Try %d: Huffman error: %v\n", tries+1, err)
|
||||
}
|
||||
huffBuff = huffBuff[:0]
|
||||
if tries == 250 {
|
||||
if debug {
|
||||
println("Huffman: Bailing out with predefined table")
|
||||
}
|
||||
|
||||
// Bail out.... Just generate something
|
||||
huffBuff = append(huffBuff, bytes.Repeat([]byte{255}, 10000)...)
|
||||
for i := 0; i < 128; i++ {
|
||||
huffBuff = append(huffBuff, byte(i))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, huff0.ErrIncompressible) {
|
||||
// Try truncating least common.
|
||||
for i, n := range remain[:] {
|
||||
if n > 0 {
|
||||
n = n / (div * (i + 1))
|
||||
if n > 0 {
|
||||
huffBuff = append(huffBuff, bytes.Repeat([]byte{byte(i)}, n)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.CompatV155 && len(huffBuff) > 0 && huffBuff[len(huffBuff)-1] != 255 {
|
||||
huffBuff = append(huffBuff, 255)
|
||||
}
|
||||
if len(huffBuff) == 0 {
|
||||
huffBuff = append(huffBuff, 0, 255)
|
||||
}
|
||||
}
|
||||
if errors.Is(err, huff0.ErrUseRLE) {
|
||||
for i, n := range remain[:] {
|
||||
n = n / (div * (i + 1))
|
||||
// Allow all entries to be represented.
|
||||
if n == 0 {
|
||||
n = 1
|
||||
}
|
||||
huffBuff = append(huffBuff, bytes.Repeat([]byte{byte(i)}, n)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
out.Write([]byte(dictMagic))
|
||||
out.Write(binary.LittleEndian.AppendUint32(nil, o.ID))
|
||||
out.Write(scratch.OutTable)
|
||||
if debug {
|
||||
println("huff table:", len(scratch.OutTable), "bytes")
|
||||
println("of table:", len(ofTable), "bytes")
|
||||
println("ml table:", len(mlTable), "bytes")
|
||||
println("ll table:", len(llTable), "bytes")
|
||||
}
|
||||
out.Write(ofTable)
|
||||
out.Write(mlTable)
|
||||
out.Write(llTable)
|
||||
out.Write(binary.LittleEndian.AppendUint32(nil, uint32(o.Offsets[0])))
|
||||
out.Write(binary.LittleEndian.AppendUint32(nil, uint32(o.Offsets[1])))
|
||||
out.Write(binary.LittleEndian.AppendUint32(nil, uint32(o.Offsets[2])))
|
||||
out.Write(hist)
|
||||
if debug {
|
||||
_, err := loadDict(out.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
i, err := InspectDictionary(out.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println("ID:", i.ID())
|
||||
println("Content size:", i.ContentSize())
|
||||
println("Encoder:", i.LitEncoder() != nil)
|
||||
println("Offsets:", i.Offsets())
|
||||
var totalSize int
|
||||
for _, b := range contents {
|
||||
totalSize += len(b)
|
||||
}
|
||||
|
||||
encWith := func(opts ...EOption) int {
|
||||
enc, err := NewWriter(nil, opts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer enc.Close()
|
||||
var dst []byte
|
||||
var totalSize int
|
||||
for _, b := range contents {
|
||||
dst = enc.EncodeAll(b, dst[:0])
|
||||
totalSize += len(dst)
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
plain := encWith(WithEncoderLevel(o.Level))
|
||||
withDict := encWith(WithEncoderLevel(o.Level), WithEncoderDict(out.Bytes()))
|
||||
println("Input size:", totalSize)
|
||||
println("Plain Compressed:", plain)
|
||||
println("Dict Compressed:", withDict)
|
||||
println("Saved:", plain-withDict, (plain-withDict)/len(contents), "bytes per input (rounded down)")
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
+33
-22
@@ -43,7 +43,7 @@ func (m *match) estBits(bitsPerByte int32) {
|
||||
if m.rep < 0 {
|
||||
ofc = ofCode(uint32(m.s-m.offset) + 3)
|
||||
} else {
|
||||
ofc = ofCode(uint32(m.rep))
|
||||
ofc = ofCode(uint32(m.rep) & 3)
|
||||
}
|
||||
// Cost, excluding
|
||||
ofTT, mlTT := fsePredefEnc[tableOffsets].ct.symbolTT[ofc], fsePredefEnc[tableMatchLengths].ct.symbolTT[mlc]
|
||||
@@ -197,12 +197,13 @@ encodeLoop:
|
||||
|
||||
// Set m to a match at offset if it looks like that will improve compression.
|
||||
improve := func(m *match, offset int32, s int32, first uint32, rep int32) {
|
||||
if s-offset >= e.maxMatchOff || load3232(src, offset) != first {
|
||||
delta := s - offset
|
||||
if delta >= e.maxMatchOff || delta <= 0 || load3232(src, offset) != first {
|
||||
return
|
||||
}
|
||||
if debugAsserts {
|
||||
if offset <= 0 {
|
||||
panic(offset)
|
||||
if offset >= s {
|
||||
panic(fmt.Sprintf("offset: %d - s:%d - rep: %d - cur :%d - max: %d", offset, s, rep, e.cur, e.maxMatchOff))
|
||||
}
|
||||
if !bytes.Equal(src[s:s+4], src[offset:offset+4]) {
|
||||
panic(fmt.Sprintf("first match mismatch: %v != %v, first: %08x", src[s:s+4], src[offset:offset+4], first))
|
||||
@@ -226,7 +227,7 @@ encodeLoop:
|
||||
}
|
||||
}
|
||||
l := 4 + e.matchlen(s+4, offset+4, src)
|
||||
if rep < 0 {
|
||||
if true {
|
||||
// Extend candidate match backwards as far as possible.
|
||||
tMin := s - e.maxMatchOff
|
||||
if tMin < 0 {
|
||||
@@ -281,6 +282,7 @@ encodeLoop:
|
||||
// Load next and check...
|
||||
e.longTable[nextHashL] = prevEntry{offset: s + e.cur, prev: candidateL.offset}
|
||||
e.table[nextHashS] = prevEntry{offset: s + e.cur, prev: candidateS.offset}
|
||||
index0 := s + 1
|
||||
|
||||
// Look far ahead, unless we have a really long match already...
|
||||
if best.length < goodEnough {
|
||||
@@ -343,8 +345,8 @@ encodeLoop:
|
||||
if best.rep > 0 {
|
||||
var seq seq
|
||||
seq.matchLen = uint32(best.length - zstdMinMatch)
|
||||
if debugAsserts && s <= nextEmit {
|
||||
panic("s <= nextEmit")
|
||||
if debugAsserts && s < nextEmit {
|
||||
panic("s < nextEmit")
|
||||
}
|
||||
addLiterals(&seq, best.s)
|
||||
|
||||
@@ -356,19 +358,16 @@ encodeLoop:
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
// Index old s + 1 -> s - 1
|
||||
index0 := s + 1
|
||||
s = best.s + best.length
|
||||
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, best.length)
|
||||
}
|
||||
break encodeLoop
|
||||
}
|
||||
|
||||
// Index skipped...
|
||||
end := s
|
||||
if s > sLimit+4 {
|
||||
end = sLimit + 4
|
||||
}
|
||||
off := index0 + e.cur
|
||||
for index0 < s {
|
||||
for index0 < end {
|
||||
cv0 := load6432(src, index0)
|
||||
h0 := hashLen(cv0, bestLongTableBits, bestLongLen)
|
||||
h1 := hashLen(cv0, bestShortTableBits, bestShortLen)
|
||||
@@ -377,6 +376,7 @@ encodeLoop:
|
||||
off++
|
||||
index0++
|
||||
}
|
||||
|
||||
switch best.rep {
|
||||
case 2, 4 | 1:
|
||||
offset1, offset2 = offset2, offset1
|
||||
@@ -385,12 +385,17 @@ encodeLoop:
|
||||
case 4 | 3:
|
||||
offset1, offset2, offset3 = offset1-1, offset1, offset2
|
||||
}
|
||||
if s >= sLimit {
|
||||
if debugEncoder {
|
||||
println("repeat ended", s, best.length)
|
||||
}
|
||||
break encodeLoop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// A 4-byte match has been found. Update recent offsets.
|
||||
// We'll later see if more than 4 bytes.
|
||||
index0 := s + 1
|
||||
s = best.s
|
||||
t := best.offset
|
||||
offset1, offset2, offset3 = s-t, offset1, offset2
|
||||
@@ -418,19 +423,25 @@ encodeLoop:
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
break encodeLoop
|
||||
|
||||
// Index old s + 1 -> s - 1 or sLimit
|
||||
end := s
|
||||
if s > sLimit-4 {
|
||||
end = sLimit - 4
|
||||
}
|
||||
|
||||
// Index old s + 1 -> s - 1
|
||||
for index0 < s {
|
||||
off := index0 + e.cur
|
||||
for index0 < end {
|
||||
cv0 := load6432(src, index0)
|
||||
h0 := hashLen(cv0, bestLongTableBits, bestLongLen)
|
||||
h1 := hashLen(cv0, bestShortTableBits, bestShortLen)
|
||||
off := index0 + e.cur
|
||||
e.longTable[h0] = prevEntry{offset: off, prev: e.longTable[h0].offset}
|
||||
e.table[h1] = prevEntry{offset: off, prev: e.table[h1].offset}
|
||||
index0++
|
||||
off++
|
||||
}
|
||||
if s >= sLimit {
|
||||
break encodeLoop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-9
@@ -145,7 +145,7 @@ encodeLoop:
|
||||
var t int32
|
||||
// We allow the encoder to optionally turn off repeat offsets across blocks
|
||||
canRepeat := len(blk.sequences) > 2
|
||||
var matched int32
|
||||
var matched, index0 int32
|
||||
|
||||
for {
|
||||
if debugAsserts && canRepeat && offset1 == 0 {
|
||||
@@ -162,6 +162,7 @@ encodeLoop:
|
||||
off := s + e.cur
|
||||
e.longTable[nextHashL] = prevEntry{offset: off, prev: candidateL.offset}
|
||||
e.table[nextHashS] = tableEntry{offset: off, val: uint32(cv)}
|
||||
index0 = s + 1
|
||||
|
||||
if canRepeat {
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
@@ -258,7 +259,6 @@ encodeLoop:
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
index0 := s + repOff2
|
||||
s += lenght + repOff2
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
@@ -498,15 +498,15 @@ encodeLoop:
|
||||
}
|
||||
|
||||
// Index match start+1 (long) -> s - 1
|
||||
index0 := s - l + 1
|
||||
off := index0 + e.cur
|
||||
for index0 < s-1 {
|
||||
cv0 := load6432(src, index0)
|
||||
cv1 := cv0 >> 8
|
||||
h0 := hashLen(cv0, betterLongTableBits, betterLongLen)
|
||||
off := index0 + e.cur
|
||||
e.longTable[h0] = prevEntry{offset: off, prev: e.longTable[h0].offset}
|
||||
e.table[hashLen(cv1, betterShortTableBits, betterShortLen)] = tableEntry{offset: off + 1, val: uint32(cv1)}
|
||||
index0 += 2
|
||||
off += 2
|
||||
}
|
||||
|
||||
cv = load6432(src, s)
|
||||
@@ -672,7 +672,7 @@ encodeLoop:
|
||||
var t int32
|
||||
// We allow the encoder to optionally turn off repeat offsets across blocks
|
||||
canRepeat := len(blk.sequences) > 2
|
||||
var matched int32
|
||||
var matched, index0 int32
|
||||
|
||||
for {
|
||||
if debugAsserts && canRepeat && offset1 == 0 {
|
||||
@@ -691,6 +691,7 @@ encodeLoop:
|
||||
e.markLongShardDirty(nextHashL)
|
||||
e.table[nextHashS] = tableEntry{offset: off, val: uint32(cv)}
|
||||
e.markShortShardDirty(nextHashS)
|
||||
index0 = s + 1
|
||||
|
||||
if canRepeat {
|
||||
if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) {
|
||||
@@ -726,7 +727,6 @@ encodeLoop:
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
// Index match start+1 (long) -> s - 1
|
||||
index0 := s + repOff
|
||||
s += lenght + repOff
|
||||
|
||||
nextEmit = s
|
||||
@@ -790,7 +790,6 @@ encodeLoop:
|
||||
}
|
||||
blk.sequences = append(blk.sequences, seq)
|
||||
|
||||
index0 := s + repOff2
|
||||
s += lenght + repOff2
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
@@ -1024,18 +1023,18 @@ encodeLoop:
|
||||
}
|
||||
|
||||
// Index match start+1 (long) -> s - 1
|
||||
index0 := s - l + 1
|
||||
off := index0 + e.cur
|
||||
for index0 < s-1 {
|
||||
cv0 := load6432(src, index0)
|
||||
cv1 := cv0 >> 8
|
||||
h0 := hashLen(cv0, betterLongTableBits, betterLongLen)
|
||||
off := index0 + e.cur
|
||||
e.longTable[h0] = prevEntry{offset: off, prev: e.longTable[h0].offset}
|
||||
e.markLongShardDirty(h0)
|
||||
h1 := hashLen(cv1, betterShortTableBits, betterShortLen)
|
||||
e.table[h1] = tableEntry{offset: off + 1, val: uint32(cv1)}
|
||||
e.markShortShardDirty(h1)
|
||||
index0 += 2
|
||||
off += 2
|
||||
}
|
||||
|
||||
cv = load6432(src, s)
|
||||
|
||||
+4
-9
@@ -227,10 +227,7 @@ func (e *Encoder) nextBlock(final bool) error {
|
||||
DictID: e.o.dict.ID(),
|
||||
}
|
||||
|
||||
dst, err := fh.appendTo(tmp[:0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst := fh.appendTo(tmp[:0])
|
||||
s.headerWritten = true
|
||||
s.wWg.Wait()
|
||||
var n2 int
|
||||
@@ -483,7 +480,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
Checksum: false,
|
||||
DictID: 0,
|
||||
}
|
||||
dst, _ = fh.appendTo(dst)
|
||||
dst = fh.appendTo(dst)
|
||||
|
||||
// Write raw block as last one only.
|
||||
var blk blockHeader
|
||||
@@ -518,10 +515,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
if len(dst) == 0 && cap(dst) == 0 && len(src) < 1<<20 && !e.o.lowMem {
|
||||
dst = make([]byte, 0, len(src))
|
||||
}
|
||||
dst, err := fh.appendTo(dst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dst = fh.appendTo(dst)
|
||||
|
||||
// If we can do everything in one block, prefer that.
|
||||
if len(src) <= e.o.blockSize {
|
||||
@@ -581,6 +575,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte {
|
||||
// Add padding with content from crypto/rand.Reader
|
||||
if e.o.pad > 0 {
|
||||
add := calcSkippableFrame(int64(len(dst)), int64(e.o.pad))
|
||||
var err error
|
||||
dst, err = skippableFrame(dst, add, rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ type frameHeader struct {
|
||||
|
||||
const maxHeaderSize = 14
|
||||
|
||||
func (f frameHeader) appendTo(dst []byte) ([]byte, error) {
|
||||
func (f frameHeader) appendTo(dst []byte) []byte {
|
||||
dst = append(dst, frameMagic...)
|
||||
var fhd uint8
|
||||
if f.Checksum {
|
||||
@@ -88,7 +88,7 @@ func (f frameHeader) appendTo(dst []byte) ([]byte, error) {
|
||||
default:
|
||||
panic("invalid fcs")
|
||||
}
|
||||
return dst, nil
|
||||
return dst
|
||||
}
|
||||
|
||||
const skippableFrameHeader = 4 + 4
|
||||
|
||||
+6
-11
@@ -245,7 +245,7 @@ func (s *sequenceDecs) decodeSync(hist []byte) error {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
var ll, mo, ml int
|
||||
if br.off > 4+((maxOffsetBits+16+16)>>3) {
|
||||
if len(br.in) > 4+((maxOffsetBits+16+16)>>3) {
|
||||
// inlined function:
|
||||
// ll, mo, ml = s.nextFast(br, llState, mlState, ofState)
|
||||
|
||||
@@ -452,18 +452,13 @@ func (s *sequenceDecs) next(br *bitReader, llState, mlState, ofState decSymbol)
|
||||
|
||||
// extra bits are stored in reverse order.
|
||||
br.fill()
|
||||
if s.maxBits <= 32 {
|
||||
mo += br.getBits(moB)
|
||||
ml += br.getBits(mlB)
|
||||
ll += br.getBits(llB)
|
||||
} else {
|
||||
mo += br.getBits(moB)
|
||||
mo += br.getBits(moB)
|
||||
if s.maxBits > 32 {
|
||||
br.fill()
|
||||
// matchlength+literal length, max 32 bits
|
||||
ml += br.getBits(mlB)
|
||||
ll += br.getBits(llB)
|
||||
|
||||
}
|
||||
// matchlength+literal length, max 32 bits
|
||||
ml += br.getBits(mlB)
|
||||
ll += br.getBits(llB)
|
||||
mo = s.adjustOffset(mo, ll, moB)
|
||||
return
|
||||
}
|
||||
|
||||
+64
-64
@@ -5,11 +5,11 @@
|
||||
// func sequenceDecs_decode_amd64(s *sequenceDecs, br *bitReader, ctx *decodeAsmContext) int
|
||||
// Requires: CMOV
|
||||
TEXT ·sequenceDecs_decode_amd64(SB), $8-32
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ 32(AX), DX
|
||||
MOVBQZX 40(AX), BX
|
||||
MOVQ 24(AX), SI
|
||||
MOVQ (AX), AX
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 24(CX), DX
|
||||
MOVBQZX 32(CX), BX
|
||||
MOVQ (CX), AX
|
||||
MOVQ 8(CX), SI
|
||||
ADDQ SI, AX
|
||||
MOVQ AX, (SP)
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -301,9 +301,9 @@ sequenceDecs_decode_amd64_match_len_ofs_ok:
|
||||
MOVQ R12, 152(AX)
|
||||
MOVQ R13, 160(AX)
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ DX, 32(AX)
|
||||
MOVB BL, 40(AX)
|
||||
MOVQ SI, 24(AX)
|
||||
MOVQ DX, 24(AX)
|
||||
MOVB BL, 32(AX)
|
||||
MOVQ SI, 8(AX)
|
||||
|
||||
// Return success
|
||||
MOVQ $0x00000000, ret+24(FP)
|
||||
@@ -336,11 +336,11 @@ error_overread:
|
||||
// func sequenceDecs_decode_56_amd64(s *sequenceDecs, br *bitReader, ctx *decodeAsmContext) int
|
||||
// Requires: CMOV
|
||||
TEXT ·sequenceDecs_decode_56_amd64(SB), $8-32
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ 32(AX), DX
|
||||
MOVBQZX 40(AX), BX
|
||||
MOVQ 24(AX), SI
|
||||
MOVQ (AX), AX
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 24(CX), DX
|
||||
MOVBQZX 32(CX), BX
|
||||
MOVQ (CX), AX
|
||||
MOVQ 8(CX), SI
|
||||
ADDQ SI, AX
|
||||
MOVQ AX, (SP)
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -603,9 +603,9 @@ sequenceDecs_decode_56_amd64_match_len_ofs_ok:
|
||||
MOVQ R12, 152(AX)
|
||||
MOVQ R13, 160(AX)
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ DX, 32(AX)
|
||||
MOVB BL, 40(AX)
|
||||
MOVQ SI, 24(AX)
|
||||
MOVQ DX, 24(AX)
|
||||
MOVB BL, 32(AX)
|
||||
MOVQ SI, 8(AX)
|
||||
|
||||
// Return success
|
||||
MOVQ $0x00000000, ret+24(FP)
|
||||
@@ -638,11 +638,11 @@ error_overread:
|
||||
// func sequenceDecs_decode_bmi2(s *sequenceDecs, br *bitReader, ctx *decodeAsmContext) int
|
||||
// Requires: BMI, BMI2, CMOV
|
||||
TEXT ·sequenceDecs_decode_bmi2(SB), $8-32
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 32(CX), AX
|
||||
MOVBQZX 40(CX), DX
|
||||
MOVQ 24(CX), BX
|
||||
MOVQ (CX), CX
|
||||
MOVQ br+8(FP), BX
|
||||
MOVQ 24(BX), AX
|
||||
MOVBQZX 32(BX), DX
|
||||
MOVQ (BX), CX
|
||||
MOVQ 8(BX), BX
|
||||
ADDQ BX, CX
|
||||
MOVQ CX, (SP)
|
||||
MOVQ ctx+16(FP), CX
|
||||
@@ -892,9 +892,9 @@ sequenceDecs_decode_bmi2_match_len_ofs_ok:
|
||||
MOVQ R11, 152(CX)
|
||||
MOVQ R12, 160(CX)
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ AX, 32(CX)
|
||||
MOVB DL, 40(CX)
|
||||
MOVQ BX, 24(CX)
|
||||
MOVQ AX, 24(CX)
|
||||
MOVB DL, 32(CX)
|
||||
MOVQ BX, 8(CX)
|
||||
|
||||
// Return success
|
||||
MOVQ $0x00000000, ret+24(FP)
|
||||
@@ -927,11 +927,11 @@ error_overread:
|
||||
// func sequenceDecs_decode_56_bmi2(s *sequenceDecs, br *bitReader, ctx *decodeAsmContext) int
|
||||
// Requires: BMI, BMI2, CMOV
|
||||
TEXT ·sequenceDecs_decode_56_bmi2(SB), $8-32
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 32(CX), AX
|
||||
MOVBQZX 40(CX), DX
|
||||
MOVQ 24(CX), BX
|
||||
MOVQ (CX), CX
|
||||
MOVQ br+8(FP), BX
|
||||
MOVQ 24(BX), AX
|
||||
MOVBQZX 32(BX), DX
|
||||
MOVQ (BX), CX
|
||||
MOVQ 8(BX), BX
|
||||
ADDQ BX, CX
|
||||
MOVQ CX, (SP)
|
||||
MOVQ ctx+16(FP), CX
|
||||
@@ -1152,9 +1152,9 @@ sequenceDecs_decode_56_bmi2_match_len_ofs_ok:
|
||||
MOVQ R11, 152(CX)
|
||||
MOVQ R12, 160(CX)
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ AX, 32(CX)
|
||||
MOVB DL, 40(CX)
|
||||
MOVQ BX, 24(CX)
|
||||
MOVQ AX, 24(CX)
|
||||
MOVB DL, 32(CX)
|
||||
MOVQ BX, 8(CX)
|
||||
|
||||
// Return success
|
||||
MOVQ $0x00000000, ret+24(FP)
|
||||
@@ -1797,11 +1797,11 @@ empty_seqs:
|
||||
// func sequenceDecs_decodeSync_amd64(s *sequenceDecs, br *bitReader, ctx *decodeSyncAsmContext) int
|
||||
// Requires: CMOV, SSE
|
||||
TEXT ·sequenceDecs_decodeSync_amd64(SB), $64-32
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ 32(AX), DX
|
||||
MOVBQZX 40(AX), BX
|
||||
MOVQ 24(AX), SI
|
||||
MOVQ (AX), AX
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 24(CX), DX
|
||||
MOVBQZX 32(CX), BX
|
||||
MOVQ (CX), AX
|
||||
MOVQ 8(CX), SI
|
||||
ADDQ SI, AX
|
||||
MOVQ AX, (SP)
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -2295,9 +2295,9 @@ handle_loop:
|
||||
|
||||
loop_finished:
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ DX, 32(AX)
|
||||
MOVB BL, 40(AX)
|
||||
MOVQ SI, 24(AX)
|
||||
MOVQ DX, 24(AX)
|
||||
MOVB BL, 32(AX)
|
||||
MOVQ SI, 8(AX)
|
||||
|
||||
// Update the context
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -2362,11 +2362,11 @@ error_not_enough_space:
|
||||
// func sequenceDecs_decodeSync_bmi2(s *sequenceDecs, br *bitReader, ctx *decodeSyncAsmContext) int
|
||||
// Requires: BMI, BMI2, CMOV, SSE
|
||||
TEXT ·sequenceDecs_decodeSync_bmi2(SB), $64-32
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 32(CX), AX
|
||||
MOVBQZX 40(CX), DX
|
||||
MOVQ 24(CX), BX
|
||||
MOVQ (CX), CX
|
||||
MOVQ br+8(FP), BX
|
||||
MOVQ 24(BX), AX
|
||||
MOVBQZX 32(BX), DX
|
||||
MOVQ (BX), CX
|
||||
MOVQ 8(BX), BX
|
||||
ADDQ BX, CX
|
||||
MOVQ CX, (SP)
|
||||
MOVQ ctx+16(FP), CX
|
||||
@@ -2818,9 +2818,9 @@ handle_loop:
|
||||
|
||||
loop_finished:
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ AX, 32(CX)
|
||||
MOVB DL, 40(CX)
|
||||
MOVQ BX, 24(CX)
|
||||
MOVQ AX, 24(CX)
|
||||
MOVB DL, 32(CX)
|
||||
MOVQ BX, 8(CX)
|
||||
|
||||
// Update the context
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -2885,11 +2885,11 @@ error_not_enough_space:
|
||||
// func sequenceDecs_decodeSync_safe_amd64(s *sequenceDecs, br *bitReader, ctx *decodeSyncAsmContext) int
|
||||
// Requires: CMOV, SSE
|
||||
TEXT ·sequenceDecs_decodeSync_safe_amd64(SB), $64-32
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ 32(AX), DX
|
||||
MOVBQZX 40(AX), BX
|
||||
MOVQ 24(AX), SI
|
||||
MOVQ (AX), AX
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 24(CX), DX
|
||||
MOVBQZX 32(CX), BX
|
||||
MOVQ (CX), AX
|
||||
MOVQ 8(CX), SI
|
||||
ADDQ SI, AX
|
||||
MOVQ AX, (SP)
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -3485,9 +3485,9 @@ handle_loop:
|
||||
|
||||
loop_finished:
|
||||
MOVQ br+8(FP), AX
|
||||
MOVQ DX, 32(AX)
|
||||
MOVB BL, 40(AX)
|
||||
MOVQ SI, 24(AX)
|
||||
MOVQ DX, 24(AX)
|
||||
MOVB BL, 32(AX)
|
||||
MOVQ SI, 8(AX)
|
||||
|
||||
// Update the context
|
||||
MOVQ ctx+16(FP), AX
|
||||
@@ -3552,11 +3552,11 @@ error_not_enough_space:
|
||||
// func sequenceDecs_decodeSync_safe_bmi2(s *sequenceDecs, br *bitReader, ctx *decodeSyncAsmContext) int
|
||||
// Requires: BMI, BMI2, CMOV, SSE
|
||||
TEXT ·sequenceDecs_decodeSync_safe_bmi2(SB), $64-32
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ 32(CX), AX
|
||||
MOVBQZX 40(CX), DX
|
||||
MOVQ 24(CX), BX
|
||||
MOVQ (CX), CX
|
||||
MOVQ br+8(FP), BX
|
||||
MOVQ 24(BX), AX
|
||||
MOVBQZX 32(BX), DX
|
||||
MOVQ (BX), CX
|
||||
MOVQ 8(BX), BX
|
||||
ADDQ BX, CX
|
||||
MOVQ CX, (SP)
|
||||
MOVQ ctx+16(FP), CX
|
||||
@@ -4110,9 +4110,9 @@ handle_loop:
|
||||
|
||||
loop_finished:
|
||||
MOVQ br+8(FP), CX
|
||||
MOVQ AX, 32(CX)
|
||||
MOVB DL, 40(CX)
|
||||
MOVQ BX, 24(CX)
|
||||
MOVQ AX, 24(CX)
|
||||
MOVB DL, 32(CX)
|
||||
MOVQ BX, 8(CX)
|
||||
|
||||
// Update the context
|
||||
MOVQ ctx+16(FP), AX
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ func (s *sequenceDecs) decode(seqs []seqVals) error {
|
||||
}
|
||||
for i := range seqs {
|
||||
var ll, mo, ml int
|
||||
if br.off > 4+((maxOffsetBits+16+16)>>3) {
|
||||
if len(br.in) > 4+((maxOffsetBits+16+16)>>3) {
|
||||
// inlined function:
|
||||
// ll, mo, ml = s.nextFast(br, llState, mlState, ofState)
|
||||
|
||||
|
||||
+2
-3
@@ -95,10 +95,9 @@ func (r *SnappyConverter) Convert(in io.Reader, w io.Writer) (int64, error) {
|
||||
var written int64
|
||||
var readHeader bool
|
||||
{
|
||||
var header []byte
|
||||
var n int
|
||||
header, r.err = frameHeader{WindowSize: snappyMaxBlockSize}.appendTo(r.buf[:0])
|
||||
header := frameHeader{WindowSize: snappyMaxBlockSize}.appendTo(r.buf[:0])
|
||||
|
||||
var n int
|
||||
n, r.err = w.Write(header)
|
||||
if r.err != nil {
|
||||
return written, r.err
|
||||
|
||||
+4
-5
@@ -10,6 +10,7 @@ Third-party patches are essential for keeping bluemonday secure and offering the
|
||||
|
||||
1. Do not vendor dependencies. As a security package, were we to vendor dependencies the projects that then vendor bluemonday may not receive the latest security updates to the dependencies. By not vendoring dependencies the project that implements bluemonday will vendor the latest version of any dependent packages. Vendoring is a project problem, not a package problem. bluemonday will be tested against the latest version of dependencies periodically and during any PR/merge.
|
||||
2. I do not care about spelling mistakes or whitespace and I do not believe that you should either. PRs therefore must be functional in their nature or be substantial and impactful if documentation or examples.
|
||||
3. This module does not participate in hacktober, please make your contributions meaningful.
|
||||
|
||||
## Submitting an Issue
|
||||
|
||||
@@ -45,8 +46,6 @@ We haven't gone for the formal "Sign a Contributor Licence Agreement" thing that
|
||||
|
||||
But we do need to know that we can accept and merge your contributions, so for now the act of contributing a pull request should be considered equivalent to agreeing to a contributor licence agreement, specifically:
|
||||
|
||||
You accept that the act of submitting code to the bluemonday project is to grant a copyright licence to the project that is perpetual, worldwide, non-exclusive, no-charge, royalty free and irrevocable.
|
||||
|
||||
You accept that all who comply with the licence of the project (BSD 3-clause) are permitted to use your contributions to the project.
|
||||
|
||||
You accept, and by submitting code do declare, that you have the legal right to grant such a licence to the project and that each of the contributions is your own original creation.
|
||||
* You accept that the act of submitting code to the bluemonday project is to grant a copyright licence to the project that is perpetual, worldwide, non-exclusive, no-charge, royalty free and irrevocable.
|
||||
* You accept that all who comply with the licence of the project (BSD 3-clause) are permitted to use your contributions to the project.
|
||||
* You accept, and by submitting code do declare, that you have the legal right to grant such a licence to the project and that each of the contributions is your own original creation.
|
||||
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
# fmt-check: Check if the source files are formated
|
||||
# build: Builds the code locally
|
||||
# vet: Vets the code
|
||||
# lint: Runs lint over the code (you do not need to fix everything)
|
||||
# staticcheck: Runs staticcheck over the code
|
||||
# test: Runs the tests
|
||||
# cover: Gives you the URL to a nice test coverage report
|
||||
#
|
||||
@@ -33,8 +33,8 @@ build:
|
||||
vet:
|
||||
@go vet
|
||||
|
||||
lint:
|
||||
@golint *.go
|
||||
staticcheck:
|
||||
@staticcheck ./...
|
||||
|
||||
test:
|
||||
@go test -v ./...
|
||||
@@ -42,7 +42,7 @@ test:
|
||||
cover: COVERAGE_FILE := coverage.out
|
||||
cover:
|
||||
@go test -coverprofile=$(COVERAGE_FILE) && \
|
||||
cover -html=$(COVERAGE_FILE) && rm $(COVERAGE_FILE)
|
||||
go tool cover -html=$(COVERAGE_FILE) && rm $(COVERAGE_FILE)
|
||||
|
||||
install:
|
||||
@go install ./...
|
||||
|
||||
+3
-2
@@ -366,10 +366,11 @@ func in(value []string, arr []string) bool {
|
||||
|
||||
func splitValues(value string) []string {
|
||||
values := strings.Split(value, ",")
|
||||
newValues := []string{}
|
||||
for _, strippedValue := range values {
|
||||
strippedValue = strings.ToLower(strings.TrimSpace(strippedValue))
|
||||
newValues = append(newValues, strings.ToLower(strings.TrimSpace(strippedValue)))
|
||||
}
|
||||
return values
|
||||
return newValues
|
||||
}
|
||||
|
||||
func GetDefaultHandler(attr string) func(string) bool {
|
||||
|
||||
+1
-1
@@ -998,7 +998,7 @@ func linkable(elementName string) bool {
|
||||
// stringInSlice returns true if needle exists in haystack
|
||||
func stringInSlice(needle string, haystack []string) bool {
|
||||
for _, straw := range haystack {
|
||||
if strings.ToLower(straw) == strings.ToLower(needle) {
|
||||
if strings.EqualFold(straw, needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ type ConfirmationBlockObject struct {
|
||||
Title *TextBlockObject `json:"title"`
|
||||
Text *TextBlockObject `json:"text"`
|
||||
Confirm *TextBlockObject `json:"confirm"`
|
||||
Deny *TextBlockObject `json:"deny"`
|
||||
Deny *TextBlockObject `json:"deny,omitempty"`
|
||||
Style Style `json:"style,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
+19
-4
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/slack-go/slack/slackutilsx"
|
||||
@@ -29,9 +30,9 @@ const (
|
||||
|
||||
type chatResponseFull struct {
|
||||
Channel string `json:"channel"`
|
||||
Timestamp string `json:"ts"` //Regular message timestamp
|
||||
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
|
||||
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id
|
||||
Timestamp string `json:"ts"` // Regular message timestamp
|
||||
MessageTimeStamp string `json:"message_ts"` // Ephemeral message timestamp
|
||||
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` // Scheduled message id
|
||||
Text string `json:"text"`
|
||||
SlackResponse
|
||||
}
|
||||
@@ -224,7 +225,7 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
|
||||
return "", "", "", err
|
||||
}
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
|
||||
api.Debugf("Sending request: %s", string(reqBody))
|
||||
api.Debugf("Sending request: %s", redactToken(reqBody))
|
||||
}
|
||||
|
||||
if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
|
||||
@@ -234,6 +235,20 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
|
||||
return response.Channel, response.getMessageTimestamp(), response.Text, response.Err()
|
||||
}
|
||||
|
||||
func redactToken(b []byte) []byte {
|
||||
// See https://api.slack.com/authentication/token-types
|
||||
// and https://api.slack.com/authentication/rotation
|
||||
re, err := regexp.Compile(`(token=x[a-z.]+)-[0-9A-Za-z-]+`)
|
||||
if err != nil {
|
||||
// The regular expression above should never result in errors,
|
||||
// but just in case, do no harm.
|
||||
return b
|
||||
}
|
||||
// Keep "token=" and the first element of the token, which identifies its type
|
||||
// (this could be useful for debugging, e.g. when using a wrong token).
|
||||
return re.ReplaceAll(b, []byte("$1-REDACTED"))
|
||||
}
|
||||
|
||||
// UnsafeApplyMsgOptions utility function for debugging/testing chat requests.
|
||||
// NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this function
|
||||
// will be supported by the library.
|
||||
|
||||
+47
@@ -296,6 +296,53 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
|
||||
return response.Channel, response.Err()
|
||||
}
|
||||
|
||||
// InviteSharedEmailsToConversation invites users to a shared channels by email
|
||||
func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(context.Background(), channelID, emails, nil)
|
||||
}
|
||||
|
||||
// InviteSharedEmailsToConversationContext invites users to a shared channels by email using context
|
||||
func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(ctx, channelID, emails, nil)
|
||||
}
|
||||
|
||||
// InviteSharedUserIDsToConversation invites users to a shared channels by user id
|
||||
func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(context.Background(), channelID, nil, userIDs)
|
||||
}
|
||||
|
||||
// InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context
|
||||
func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) {
|
||||
return api.inviteSharedToConversationHelper(ctx, channelID, nil, userIDs)
|
||||
}
|
||||
|
||||
// inviteSharedToConversationHelper invites emails or userIDs to a channel with a custom context.
|
||||
// This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation.
|
||||
// It accepts either emails or userIDs, but not both.
|
||||
func (api *Client) inviteSharedToConversationHelper(ctx context.Context, channelID string, emails []string, userIDs []string) (string, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel": {channelID},
|
||||
}
|
||||
if len(emails) > 0 {
|
||||
values.Add("emails", strings.Join(emails, ","))
|
||||
} else if len(userIDs) > 0 {
|
||||
values.Add("user_ids", strings.Join(userIDs, ","))
|
||||
}
|
||||
response := struct {
|
||||
SlackResponse
|
||||
InviteID string `json:"invite_id"`
|
||||
IsLegacySharedChannel bool `json:"is_legacy_shared_channel"`
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.inviteShared", values, &response)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
return response.InviteID, response.IsLegacySharedChannel, response.Err()
|
||||
}
|
||||
|
||||
// KickUserFromConversation removes a user from a conversation
|
||||
func (api *Client) KickUserFromConversation(channelID string, user string) error {
|
||||
return api.KickUserFromConversationContext(context.Background(), channelID, user)
|
||||
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Manifest is an application manifest schema
|
||||
type Manifest struct {
|
||||
Metadata ManifestMetadata `json:"_metadata,omitempty" yaml:"_metadata,omitempty"`
|
||||
Display Display `json:"display_information" yaml:"display_information"`
|
||||
Settings Settings `json:"settings,omitempty" yaml:"settings,omitempty"`
|
||||
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
|
||||
OAuthConfig OAuthConfig `json:"oauth_config,omitempty" yaml:"oauth_config,omitempty"`
|
||||
}
|
||||
|
||||
// CreateManifest creates an app from an app manifest
|
||||
func (api *Client) CreateManifest(manifest *Manifest, token string) (*ManifestResponse, error) {
|
||||
return api.CreateManifestContext(context.Background(), manifest, token)
|
||||
}
|
||||
|
||||
// CreateManifestContext creates an app from an app manifest with a custom context
|
||||
func (api *Client) CreateManifestContext(ctx context.Context, manifest *Manifest, token string) (*ManifestResponse, error) {
|
||||
if token == "" {
|
||||
token = api.configToken
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {token},
|
||||
"manifest": {string(jsonBytes)},
|
||||
}
|
||||
|
||||
response := &ManifestResponse{}
|
||||
err = api.postMethod(ctx, "apps.manifest.create", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// DeleteManifest permanently deletes an app created through app manifests
|
||||
func (api *Client) DeleteManifest(token string, appId string) (*SlackResponse, error) {
|
||||
return api.DeleteManifestContext(context.Background(), token, appId)
|
||||
}
|
||||
|
||||
// DeleteManifestContext permanently deletes an app created through app manifests with a custom context
|
||||
func (api *Client) DeleteManifestContext(ctx context.Context, token string, appId string) (*SlackResponse, error) {
|
||||
if token == "" {
|
||||
token = api.configToken
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {token},
|
||||
"app_id": {appId},
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
err := api.postMethod(ctx, "apps.manifest.delete", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// ExportManifest exports an app manifest from an existing app
|
||||
func (api *Client) ExportManifest(token string, appId string) (*Manifest, error) {
|
||||
return api.ExportManifestContext(context.Background(), token, appId)
|
||||
}
|
||||
|
||||
// ExportManifestContext exports an app manifest from an existing app with a custom context
|
||||
func (api *Client) ExportManifestContext(ctx context.Context, token string, appId string) (*Manifest, error) {
|
||||
if token == "" {
|
||||
token = api.configToken
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {token},
|
||||
"app_id": {appId},
|
||||
}
|
||||
|
||||
response := &ExportManifestResponse{}
|
||||
err := api.postMethod(ctx, "apps.manifest.export", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Manifest, response.Err()
|
||||
}
|
||||
|
||||
// UpdateManifest updates an app from an app manifest
|
||||
func (api *Client) UpdateManifest(manifest *Manifest, token string, appId string) (*UpdateManifestResponse, error) {
|
||||
return api.UpdateManifestContext(context.Background(), manifest, token, appId)
|
||||
}
|
||||
|
||||
// UpdateManifestContext updates an app from an app manifest with a custom context
|
||||
func (api *Client) UpdateManifestContext(ctx context.Context, manifest *Manifest, token string, appId string) (*UpdateManifestResponse, error) {
|
||||
if token == "" {
|
||||
token = api.configToken
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {token},
|
||||
"app_id": {appId},
|
||||
"manifest": {string(jsonBytes)},
|
||||
}
|
||||
|
||||
response := &UpdateManifestResponse{}
|
||||
err = api.postMethod(ctx, "apps.manifest.update", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// ValidateManifest sends a request to apps.manifest.validate to validate your app manifest
|
||||
func (api *Client) ValidateManifest(manifest *Manifest, token string, appId string) (*ManifestResponse, error) {
|
||||
return api.ValidateManifestContext(context.Background(), manifest, token, appId)
|
||||
}
|
||||
|
||||
// ValidateManifestContext sends a request to apps.manifest.validate to validate your app manifest with a custom context
|
||||
func (api *Client) ValidateManifestContext(ctx context.Context, manifest *Manifest, token string, appId string) (*ManifestResponse, error) {
|
||||
if token == "" {
|
||||
token = api.configToken
|
||||
}
|
||||
|
||||
// Marshal manifest into string
|
||||
jsonBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {token},
|
||||
"manifest": {string(jsonBytes)},
|
||||
}
|
||||
|
||||
if appId != "" {
|
||||
values.Add("app_id", appId)
|
||||
}
|
||||
|
||||
response := &ManifestResponse{}
|
||||
err = api.postMethod(ctx, "apps.manifest.validate", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// ManifestMetadata is a group of settings that describe the manifest
|
||||
type ManifestMetadata struct {
|
||||
MajorVersion int `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion int `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
}
|
||||
|
||||
// Display is a group of settings that describe parts of an app's appearance within Slack
|
||||
type Display struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
LongDescription string `json:"long_description,omitempty" yaml:"long_description,omitempty"`
|
||||
BackgroundColor string `json:"background_color,omitempty" yaml:"background_color,omitempty"`
|
||||
}
|
||||
|
||||
// Settings is a group of settings corresponding to the Settings section of the app config pages.
|
||||
type Settings struct {
|
||||
AllowedIPAddressRanges []string `json:"allowed_ip_address_ranges,omitempty" yaml:"allowed_ip_address_ranges,omitempty"`
|
||||
EventSubscriptions EventSubscriptions `json:"event_subscriptions,omitempty" yaml:"event_subscriptions,omitempty"`
|
||||
Interactivity Interactivity `json:"interactivity,omitempty" yaml:"interactivity,omitempty"`
|
||||
OrgDeployEnabled bool `json:"org_deploy_enabled,omitempty" yaml:"org_deploy_enabled,omitempty"`
|
||||
SocketModeEnabled bool `json:"socket_mode_enabled,omitempty" yaml:"socket_mode_enabled,omitempty"`
|
||||
}
|
||||
|
||||
// EventSubscriptions is a group of settings that describe the Events API configuration
|
||||
type EventSubscriptions struct {
|
||||
RequestUrl string `json:"request_url,omitempty" yaml:"request_url,omitempty"`
|
||||
BotEvents []string `json:"bot_events,omitempty" yaml:"bot_events,omitempty"`
|
||||
UserEvents []string `json:"user_events,omitempty" yaml:"user_events,omitempty"`
|
||||
}
|
||||
|
||||
// Interactivity is a group of settings that describe the interactivity configuration
|
||||
type Interactivity struct {
|
||||
IsEnabled bool `json:"is_enabled" yaml:"is_enabled"`
|
||||
RequestUrl string `json:"request_url,omitempty" yaml:"request_url,omitempty"`
|
||||
MessageMenuOptionsUrl string `json:"message_menu_options_url,omitempty" yaml:"message_menu_options_url,omitempty"`
|
||||
}
|
||||
|
||||
// Features is a group of settings corresponding to the Features section of the app config pages
|
||||
type Features struct {
|
||||
AppHome AppHome `json:"app_home,omitempty" yaml:"app_home,omitempty"`
|
||||
BotUser BotUser `json:"bot_user,omitempty" yaml:"bot_user,omitempty"`
|
||||
Shortcuts []Shortcut `json:"shortcuts,omitempty" yaml:"shortcuts,omitempty"`
|
||||
SlashCommands []ManifestSlashCommand `json:"slash_commands,omitempty" yaml:"slash_commands,omitempty"`
|
||||
WorkflowSteps []WorkflowStep `json:"workflow_steps,omitempty" yaml:"workflow_steps,omitempty"`
|
||||
}
|
||||
|
||||
// AppHome is a group of settings that describe the App Home configuration
|
||||
type AppHome struct {
|
||||
HomeTabEnabled bool `json:"home_tab_enabled,omitempty" yaml:"home_tab_enabled,omitempty"`
|
||||
MessagesTabEnabled bool `json:"messages_tab_enabled,omitempty" yaml:"messages_tab_enabled,omitempty"`
|
||||
MessagesTabReadOnlyEnabled bool `json:"messages_tab_read_only_enabled,omitempty" yaml:"messages_tab_read_only_enabled,omitempty"`
|
||||
}
|
||||
|
||||
// BotUser is a group of settings that describe bot user configuration
|
||||
type BotUser struct {
|
||||
DisplayName string `json:"display_name" yaml:"display_name"`
|
||||
AlwaysOnline bool `json:"always_online,omitempty" yaml:"always_online,omitempty"`
|
||||
}
|
||||
|
||||
// Shortcut is a group of settings that describes shortcut configuration
|
||||
type Shortcut struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
CallbackID string `json:"callback_id" yaml:"callback_id"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Type ShortcutType `json:"type" yaml:"type"`
|
||||
}
|
||||
|
||||
// ShortcutType is a new string type for the available types of shortcuts
|
||||
type ShortcutType string
|
||||
|
||||
const (
|
||||
MessageShortcut ShortcutType = "message"
|
||||
GlobalShortcut ShortcutType = "global"
|
||||
)
|
||||
|
||||
// ManifestSlashCommand is a group of settings that describes slash command configuration
|
||||
type ManifestSlashCommand struct {
|
||||
Command string `json:"command" yaml:"command"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
ShouldEscape bool `json:"should_escape,omitempty" yaml:"should_escape,omitempty"`
|
||||
Url string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
UsageHint string `json:"usage_hint,omitempty" yaml:"usage_hint,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStep is a group of settings that describes workflow steps configuration
|
||||
type WorkflowStep struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
CallbackID string `json:"callback_id" yaml:"callback_id"`
|
||||
}
|
||||
|
||||
// OAuthConfig is a group of settings that describe OAuth configuration for the app
|
||||
type OAuthConfig struct {
|
||||
RedirectUrls []string `json:"redirect_urls,omitempty" yaml:"redirect_urls,omitempty"`
|
||||
Scopes OAuthScopes `json:"scopes,omitempty" yaml:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
// OAuthScopes is a group of settings that describe permission scopes configuration
|
||||
type OAuthScopes struct {
|
||||
Bot []string `json:"bot,omitempty" yaml:"bot,omitempty"`
|
||||
User []string `json:"user,omitempty" yaml:"user,omitempty"`
|
||||
}
|
||||
|
||||
// ManifestResponse is the response returned by the API for apps.manifest.x endpoints
|
||||
type ManifestResponse struct {
|
||||
Errors []ManifestValidationError `json:"errors,omitempty"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// ManifestValidationError is an error message returned for invalid manifests
|
||||
type ManifestValidationError struct {
|
||||
Message string `json:"message"`
|
||||
Pointer string `json:"pointer"`
|
||||
}
|
||||
|
||||
type ExportManifestResponse struct {
|
||||
Manifest Manifest `json:"manifest,omitempty"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
type UpdateManifestResponse struct {
|
||||
AppId string `json:"app_id,omitempty"`
|
||||
PermissionsUpdated bool `json:"permissions_updated,omitempty"`
|
||||
ManifestResponse
|
||||
}
|
||||
+18
-6
@@ -57,12 +57,14 @@ type authTestResponseFull struct {
|
||||
type ParamOption func(*url.Values)
|
||||
|
||||
type Client struct {
|
||||
token string
|
||||
appLevelToken string
|
||||
endpoint string
|
||||
debug bool
|
||||
log ilogger
|
||||
httpclient httpClient
|
||||
token string
|
||||
appLevelToken string
|
||||
configToken string
|
||||
configRefreshToken string
|
||||
endpoint string
|
||||
debug bool
|
||||
log ilogger
|
||||
httpclient httpClient
|
||||
}
|
||||
|
||||
// Option defines an option for a Client
|
||||
@@ -99,6 +101,16 @@ func OptionAppLevelToken(token string) func(*Client) {
|
||||
return func(c *Client) { c.appLevelToken = token }
|
||||
}
|
||||
|
||||
// OptionConfigToken sets a configuration token for the client.
|
||||
func OptionConfigToken(token string) func(*Client) {
|
||||
return func(c *Client) { c.configToken = token }
|
||||
}
|
||||
|
||||
// OptionConfigRefreshToken sets a configuration refresh token for the client.
|
||||
func OptionConfigRefreshToken(token string) func(*Client) {
|
||||
return func(c *Client) { c.configRefreshToken = token }
|
||||
}
|
||||
|
||||
// New builds a slack client from the provided token and options.
|
||||
func New(token string, options ...Option) *Client {
|
||||
s := &Client{
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// RotateTokens exchanges a refresh token for a new app configuration token
|
||||
func (api *Client) RotateTokens(configToken string, refreshToken string) (*TokenResponse, error) {
|
||||
return api.RotateTokensContext(context.Background(), configToken, refreshToken)
|
||||
}
|
||||
|
||||
// RotateTokensContext exchanges a refresh token for a new app configuration token with a custom context
|
||||
func (api *Client) RotateTokensContext(ctx context.Context, configToken string, refreshToken string) (*TokenResponse, error) {
|
||||
if configToken == "" {
|
||||
configToken = api.configToken
|
||||
}
|
||||
|
||||
if refreshToken == "" {
|
||||
refreshToken = api.configRefreshToken
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"refresh_token": {refreshToken},
|
||||
}
|
||||
|
||||
response := &TokenResponse{}
|
||||
err := api.getMethod(ctx, "tooling.tokens.rotate", configToken, values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// UpdateConfigTokens replaces the configuration tokens in the client with those returned by the API
|
||||
func (api *Client) UpdateConfigTokens(response *TokenResponse) {
|
||||
api.configToken = response.Token
|
||||
api.configRefreshToken = response.RefreshToken
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
TeamId string `json:"team_id,omitempty"`
|
||||
UserId string `json:"user_id,omitempty"`
|
||||
IssuedAt uint64 `json:"iat,omitempty"`
|
||||
ExpiresAt uint64 `json:"exp,omitempty"`
|
||||
SlackResponse
|
||||
}
|
||||
+2
-13
@@ -1,22 +1,11 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
msAsync = 1 << iota // perform asynchronous writes
|
||||
msSync // perform synchronous writes
|
||||
msInvalidate // invalidate cached data
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func msync(db *DB) error {
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
return unix.Msync(db.data[:db.datasz], unix.MS_INVALIDATE)
|
||||
}
|
||||
|
||||
func fdatasync(db *DB) error {
|
||||
|
||||
+37
-9
@@ -57,6 +57,12 @@ const (
|
||||
// All data access is performed through transactions which can be obtained through the DB.
|
||||
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
|
||||
type DB struct {
|
||||
// Put `stats` at the first field to ensure it's 64-bit aligned. Note that
|
||||
// the first word in an allocated struct can be relied upon to be 64-bit
|
||||
// aligned. Refer to https://pkg.go.dev/sync/atomic#pkg-note-BUG. Also
|
||||
// refer to discussion in https://github.com/etcd-io/bbolt/issues/577.
|
||||
stats Stats
|
||||
|
||||
// When enabled, the database will perform a Check() after every commit.
|
||||
// A panic is issued if the database is in an inconsistent state. This
|
||||
// flag has a large performance impact so it should only be used for
|
||||
@@ -147,7 +153,6 @@ type DB struct {
|
||||
opened bool
|
||||
rwtx *Tx
|
||||
txs []*Tx
|
||||
stats Stats
|
||||
|
||||
freelist *freelist
|
||||
freelistLoad sync.Once
|
||||
@@ -424,7 +429,7 @@ func (db *DB) hasSyncedFreelist() bool {
|
||||
|
||||
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
||||
// minsz is the minimum size that the new mmap can be.
|
||||
func (db *DB) mmap(minsz int) error {
|
||||
func (db *DB) mmap(minsz int) (err error) {
|
||||
db.mmaplock.Lock()
|
||||
defer db.mmaplock.Unlock()
|
||||
|
||||
@@ -459,17 +464,27 @@ func (db *DB) mmap(minsz int) error {
|
||||
}
|
||||
|
||||
// Unmap existing data before continuing.
|
||||
if err := db.munmap(); err != nil {
|
||||
if err = db.munmap(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Memory-map the data file as a byte slice.
|
||||
// gofail: var mapError string
|
||||
// return errors.New(mapError)
|
||||
if err := mmap(db, size); err != nil {
|
||||
if err = mmap(db, size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform unmmap on any error to reset all data fields:
|
||||
// dataref, data, datasz, meta0 and meta1.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if unmapErr := db.munmap(); unmapErr != nil {
|
||||
err = fmt.Errorf("%w; rollback unmap also failed: %v", err, unmapErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if db.Mlock {
|
||||
// Don't allow swapping of data file
|
||||
if err := db.mlock(fileSize); err != nil {
|
||||
@@ -553,6 +568,8 @@ func (db *DB) mmapSize(size int) (int, error) {
|
||||
}
|
||||
|
||||
func (db *DB) munlock(fileSize int) error {
|
||||
// gofail: var munlockError string
|
||||
// return errors.New(munlockError)
|
||||
if err := munlock(db, fileSize); err != nil {
|
||||
return fmt.Errorf("munlock error: " + err.Error())
|
||||
}
|
||||
@@ -560,6 +577,8 @@ func (db *DB) munlock(fileSize int) error {
|
||||
}
|
||||
|
||||
func (db *DB) mlock(fileSize int) error {
|
||||
// gofail: var mlockError string
|
||||
// return errors.New(mlockError)
|
||||
if err := mlock(db, fileSize); err != nil {
|
||||
return fmt.Errorf("mlock error: " + err.Error())
|
||||
}
|
||||
@@ -649,9 +668,10 @@ func (db *DB) close() error {
|
||||
// Clear ops.
|
||||
db.ops.writeAt = nil
|
||||
|
||||
var errs []error
|
||||
// Close the mmap.
|
||||
if err := db.munmap(); err != nil {
|
||||
return err
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Close file handles.
|
||||
@@ -660,18 +680,22 @@ func (db *DB) close() error {
|
||||
if !db.readOnly {
|
||||
// Unlock the file.
|
||||
if err := funlock(db); err != nil {
|
||||
return fmt.Errorf("bolt.Close(): funlock error: %w", err)
|
||||
errs = append(errs, fmt.Errorf("bolt.Close(): funlock error: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file descriptor.
|
||||
if err := db.file.Close(); err != nil {
|
||||
return fmt.Errorf("db file close: %s", err)
|
||||
errs = append(errs, fmt.Errorf("db file close: %w", err))
|
||||
}
|
||||
db.file = nil
|
||||
}
|
||||
|
||||
db.path = ""
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1263,6 +1287,12 @@ var DefaultOptions = &Options{
|
||||
|
||||
// Stats represents statistics about the database.
|
||||
type Stats struct {
|
||||
// Put `TxStats` at the first field to ensure it's 64-bit aligned. Note
|
||||
// that the first word in an allocated struct can be relied upon to be
|
||||
// 64-bit aligned. Refer to https://pkg.go.dev/sync/atomic#pkg-note-BUG.
|
||||
// Also refer to discussion in https://github.com/etcd-io/bbolt/issues/577.
|
||||
TxStats TxStats // global, ongoing stats.
|
||||
|
||||
// Freelist stats
|
||||
FreePageN int // total number of free pages on the freelist
|
||||
PendingPageN int // total number of pending pages on the freelist
|
||||
@@ -1272,8 +1302,6 @@ type Stats struct {
|
||||
// Transaction stats
|
||||
TxN int // total number of started read transactions
|
||||
OpenTxN int // number of currently open read transactions
|
||||
|
||||
TxStats TxStats // global, ongoing stats.
|
||||
}
|
||||
|
||||
// Sub calculates and returns the difference between two sets of database stats.
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package bsoncodec
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Runtime check that the kind encoder and decoder caches can store any valid
|
||||
// reflect.Kind constant.
|
||||
func init() {
|
||||
if s := reflect.Kind(len(kindEncoderCache{}.entries)).String(); s != "kind27" {
|
||||
panic("The capacity of kindEncoderCache is too small.\n" +
|
||||
"This is due to a new type being added to reflect.Kind.")
|
||||
}
|
||||
}
|
||||
|
||||
// statically assert array size
|
||||
var _ = (kindEncoderCache{}).entries[reflect.UnsafePointer]
|
||||
var _ = (kindDecoderCache{}).entries[reflect.UnsafePointer]
|
||||
|
||||
type typeEncoderCache struct {
|
||||
cache sync.Map // map[reflect.Type]ValueEncoder
|
||||
}
|
||||
|
||||
func (c *typeEncoderCache) Store(rt reflect.Type, enc ValueEncoder) {
|
||||
c.cache.Store(rt, enc)
|
||||
}
|
||||
|
||||
func (c *typeEncoderCache) Load(rt reflect.Type) (ValueEncoder, bool) {
|
||||
if v, _ := c.cache.Load(rt); v != nil {
|
||||
return v.(ValueEncoder), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *typeEncoderCache) LoadOrStore(rt reflect.Type, enc ValueEncoder) ValueEncoder {
|
||||
if v, loaded := c.cache.LoadOrStore(rt, enc); loaded {
|
||||
enc = v.(ValueEncoder)
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
func (c *typeEncoderCache) Clone() *typeEncoderCache {
|
||||
cc := new(typeEncoderCache)
|
||||
c.cache.Range(func(k, v interface{}) bool {
|
||||
if k != nil && v != nil {
|
||||
cc.cache.Store(k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return cc
|
||||
}
|
||||
|
||||
type typeDecoderCache struct {
|
||||
cache sync.Map // map[reflect.Type]ValueDecoder
|
||||
}
|
||||
|
||||
func (c *typeDecoderCache) Store(rt reflect.Type, dec ValueDecoder) {
|
||||
c.cache.Store(rt, dec)
|
||||
}
|
||||
|
||||
func (c *typeDecoderCache) Load(rt reflect.Type) (ValueDecoder, bool) {
|
||||
if v, _ := c.cache.Load(rt); v != nil {
|
||||
return v.(ValueDecoder), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *typeDecoderCache) LoadOrStore(rt reflect.Type, dec ValueDecoder) ValueDecoder {
|
||||
if v, loaded := c.cache.LoadOrStore(rt, dec); loaded {
|
||||
dec = v.(ValueDecoder)
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
func (c *typeDecoderCache) Clone() *typeDecoderCache {
|
||||
cc := new(typeDecoderCache)
|
||||
c.cache.Range(func(k, v interface{}) bool {
|
||||
if k != nil && v != nil {
|
||||
cc.cache.Store(k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return cc
|
||||
}
|
||||
|
||||
// atomic.Value requires that all calls to Store() have the same concrete type
|
||||
// so we wrap the ValueEncoder with a kindEncoderCacheEntry to ensure the type
|
||||
// is always the same (since different concrete types may implement the
|
||||
// ValueEncoder interface).
|
||||
type kindEncoderCacheEntry struct {
|
||||
enc ValueEncoder
|
||||
}
|
||||
|
||||
type kindEncoderCache struct {
|
||||
entries [reflect.UnsafePointer + 1]atomic.Value // *kindEncoderCacheEntry
|
||||
}
|
||||
|
||||
func (c *kindEncoderCache) Store(rt reflect.Kind, enc ValueEncoder) {
|
||||
if enc != nil && rt < reflect.Kind(len(c.entries)) {
|
||||
c.entries[rt].Store(&kindEncoderCacheEntry{enc: enc})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *kindEncoderCache) Load(rt reflect.Kind) (ValueEncoder, bool) {
|
||||
if rt < reflect.Kind(len(c.entries)) {
|
||||
if ent, ok := c.entries[rt].Load().(*kindEncoderCacheEntry); ok {
|
||||
return ent.enc, ent.enc != nil
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *kindEncoderCache) Clone() *kindEncoderCache {
|
||||
cc := new(kindEncoderCache)
|
||||
for i, v := range c.entries {
|
||||
if val := v.Load(); val != nil {
|
||||
cc.entries[i].Store(val)
|
||||
}
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
// atomic.Value requires that all calls to Store() have the same concrete type
|
||||
// so we wrap the ValueDecoder with a kindDecoderCacheEntry to ensure the type
|
||||
// is always the same (since different concrete types may implement the
|
||||
// ValueDecoder interface).
|
||||
type kindDecoderCacheEntry struct {
|
||||
dec ValueDecoder
|
||||
}
|
||||
|
||||
type kindDecoderCache struct {
|
||||
entries [reflect.UnsafePointer + 1]atomic.Value // *kindDecoderCacheEntry
|
||||
}
|
||||
|
||||
func (c *kindDecoderCache) Store(rt reflect.Kind, dec ValueDecoder) {
|
||||
if rt < reflect.Kind(len(c.entries)) {
|
||||
c.entries[rt].Store(&kindDecoderCacheEntry{dec: dec})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *kindDecoderCache) Load(rt reflect.Kind) (ValueDecoder, bool) {
|
||||
if rt < reflect.Kind(len(c.entries)) {
|
||||
if ent, ok := c.entries[rt].Load().(*kindDecoderCacheEntry); ok {
|
||||
return ent.dec, ent.dec != nil
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *kindDecoderCache) Clone() *kindDecoderCache {
|
||||
cc := new(kindDecoderCache)
|
||||
for i, v := range c.entries {
|
||||
if val := v.Load(); val != nil {
|
||||
cc.entries[i].Store(val)
|
||||
}
|
||||
}
|
||||
return cc
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
var (
|
||||
defaultValueDecoders DefaultValueDecoders
|
||||
errCannotTruncate = errors.New("float64 can only be truncated to an integer type when truncation is enabled")
|
||||
errCannotTruncate = errors.New("float64 can only be truncated to a lower precision type when truncation is enabled")
|
||||
)
|
||||
|
||||
type decodeBinaryError struct {
|
||||
|
||||
+22
-37
@@ -8,7 +8,6 @@ package bsoncodec
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
@@ -22,9 +21,8 @@ var _ ValueDecoder = &PointerCodec{}
|
||||
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with the
|
||||
// PointerCodec registered.
|
||||
type PointerCodec struct {
|
||||
ecache map[reflect.Type]ValueEncoder
|
||||
dcache map[reflect.Type]ValueDecoder
|
||||
l sync.RWMutex
|
||||
ecache typeEncoderCache
|
||||
dcache typeDecoderCache
|
||||
}
|
||||
|
||||
// NewPointerCodec returns a PointerCodec that has been initialized.
|
||||
@@ -32,10 +30,7 @@ type PointerCodec struct {
|
||||
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with the
|
||||
// PointerCodec registered.
|
||||
func NewPointerCodec() *PointerCodec {
|
||||
return &PointerCodec{
|
||||
ecache: make(map[reflect.Type]ValueEncoder),
|
||||
dcache: make(map[reflect.Type]ValueDecoder),
|
||||
}
|
||||
return &PointerCodec{}
|
||||
}
|
||||
|
||||
// EncodeValue handles encoding a pointer by either encoding it to BSON Null if the pointer is nil
|
||||
@@ -52,24 +47,19 @@ func (pc *PointerCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val
|
||||
return vw.WriteNull()
|
||||
}
|
||||
|
||||
pc.l.RLock()
|
||||
enc, ok := pc.ecache[val.Type()]
|
||||
pc.l.RUnlock()
|
||||
if ok {
|
||||
if enc == nil {
|
||||
return ErrNoEncoder{Type: val.Type()}
|
||||
typ := val.Type()
|
||||
if v, ok := pc.ecache.Load(typ); ok {
|
||||
if v == nil {
|
||||
return ErrNoEncoder{Type: typ}
|
||||
}
|
||||
return enc.EncodeValue(ec, vw, val.Elem())
|
||||
return v.EncodeValue(ec, vw, val.Elem())
|
||||
}
|
||||
|
||||
enc, err := ec.LookupEncoder(val.Type().Elem())
|
||||
pc.l.Lock()
|
||||
pc.ecache[val.Type()] = enc
|
||||
pc.l.Unlock()
|
||||
// TODO(charlie): handle concurrent requests for the same type
|
||||
enc, err := ec.LookupEncoder(typ.Elem())
|
||||
enc = pc.ecache.LoadOrStore(typ, enc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return enc.EncodeValue(ec, vw, val.Elem())
|
||||
}
|
||||
|
||||
@@ -80,36 +70,31 @@ func (pc *PointerCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val
|
||||
return ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: val}
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
if vr.Type() == bsontype.Null {
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
val.Set(reflect.Zero(typ))
|
||||
return vr.ReadNull()
|
||||
}
|
||||
if vr.Type() == bsontype.Undefined {
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
val.Set(reflect.Zero(typ))
|
||||
return vr.ReadUndefined()
|
||||
}
|
||||
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
val.Set(reflect.New(typ.Elem()))
|
||||
}
|
||||
|
||||
pc.l.RLock()
|
||||
dec, ok := pc.dcache[val.Type()]
|
||||
pc.l.RUnlock()
|
||||
if ok {
|
||||
if dec == nil {
|
||||
return ErrNoDecoder{Type: val.Type()}
|
||||
if v, ok := pc.dcache.Load(typ); ok {
|
||||
if v == nil {
|
||||
return ErrNoDecoder{Type: typ}
|
||||
}
|
||||
return dec.DecodeValue(dc, vr, val.Elem())
|
||||
return v.DecodeValue(dc, vr, val.Elem())
|
||||
}
|
||||
|
||||
dec, err := dc.LookupDecoder(val.Type().Elem())
|
||||
pc.l.Lock()
|
||||
pc.dcache[val.Type()] = dec
|
||||
pc.l.Unlock()
|
||||
// TODO(charlie): handle concurrent requests for the same type
|
||||
dec, err := dc.LookupDecoder(typ.Elem())
|
||||
dec = pc.dcache.LoadOrStore(typ, dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dec.DecodeValue(dc, vr, val.Elem())
|
||||
}
|
||||
|
||||
+55
-110
@@ -216,72 +216,42 @@ func (rb *RegistryBuilder) RegisterTypeMapEntry(bt bsontype.Type, rt reflect.Typ
|
||||
//
|
||||
// Deprecated: Use NewRegistry instead.
|
||||
func (rb *RegistryBuilder) Build() *Registry {
|
||||
registry := new(Registry)
|
||||
|
||||
registry.typeEncoders = make(map[reflect.Type]ValueEncoder, len(rb.registry.typeEncoders))
|
||||
for t, enc := range rb.registry.typeEncoders {
|
||||
registry.typeEncoders[t] = enc
|
||||
r := &Registry{
|
||||
interfaceEncoders: append([]interfaceValueEncoder(nil), rb.registry.interfaceEncoders...),
|
||||
interfaceDecoders: append([]interfaceValueDecoder(nil), rb.registry.interfaceDecoders...),
|
||||
typeEncoders: rb.registry.typeEncoders.Clone(),
|
||||
typeDecoders: rb.registry.typeDecoders.Clone(),
|
||||
kindEncoders: rb.registry.kindEncoders.Clone(),
|
||||
kindDecoders: rb.registry.kindDecoders.Clone(),
|
||||
}
|
||||
|
||||
registry.typeDecoders = make(map[reflect.Type]ValueDecoder, len(rb.registry.typeDecoders))
|
||||
for t, dec := range rb.registry.typeDecoders {
|
||||
registry.typeDecoders[t] = dec
|
||||
}
|
||||
|
||||
registry.interfaceEncoders = make([]interfaceValueEncoder, len(rb.registry.interfaceEncoders))
|
||||
copy(registry.interfaceEncoders, rb.registry.interfaceEncoders)
|
||||
|
||||
registry.interfaceDecoders = make([]interfaceValueDecoder, len(rb.registry.interfaceDecoders))
|
||||
copy(registry.interfaceDecoders, rb.registry.interfaceDecoders)
|
||||
|
||||
registry.kindEncoders = make(map[reflect.Kind]ValueEncoder)
|
||||
for kind, enc := range rb.registry.kindEncoders {
|
||||
registry.kindEncoders[kind] = enc
|
||||
}
|
||||
|
||||
registry.kindDecoders = make(map[reflect.Kind]ValueDecoder)
|
||||
for kind, dec := range rb.registry.kindDecoders {
|
||||
registry.kindDecoders[kind] = dec
|
||||
}
|
||||
|
||||
registry.typeMap = make(map[bsontype.Type]reflect.Type)
|
||||
for bt, rt := range rb.registry.typeMap {
|
||||
registry.typeMap[bt] = rt
|
||||
}
|
||||
|
||||
return registry
|
||||
rb.registry.typeMap.Range(func(k, v interface{}) bool {
|
||||
if k != nil && v != nil {
|
||||
r.typeMap.Store(k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// A Registry is used to store and retrieve codecs for types and interfaces. This type is the main
|
||||
// typed passed around and Encoders and Decoders are constructed from it.
|
||||
type Registry struct {
|
||||
typeEncoders map[reflect.Type]ValueEncoder
|
||||
typeDecoders map[reflect.Type]ValueDecoder
|
||||
|
||||
interfaceEncoders []interfaceValueEncoder
|
||||
interfaceDecoders []interfaceValueDecoder
|
||||
|
||||
kindEncoders map[reflect.Kind]ValueEncoder
|
||||
kindDecoders map[reflect.Kind]ValueDecoder
|
||||
|
||||
typeMap map[bsontype.Type]reflect.Type
|
||||
|
||||
mu sync.RWMutex
|
||||
typeEncoders *typeEncoderCache
|
||||
typeDecoders *typeDecoderCache
|
||||
kindEncoders *kindEncoderCache
|
||||
kindDecoders *kindDecoderCache
|
||||
typeMap sync.Map // map[bsontype.Type]reflect.Type
|
||||
}
|
||||
|
||||
// NewRegistry creates a new empty Registry.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
typeEncoders: make(map[reflect.Type]ValueEncoder),
|
||||
typeDecoders: make(map[reflect.Type]ValueDecoder),
|
||||
|
||||
interfaceEncoders: make([]interfaceValueEncoder, 0),
|
||||
interfaceDecoders: make([]interfaceValueDecoder, 0),
|
||||
|
||||
kindEncoders: make(map[reflect.Kind]ValueEncoder),
|
||||
kindDecoders: make(map[reflect.Kind]ValueDecoder),
|
||||
|
||||
typeMap: make(map[bsontype.Type]reflect.Type),
|
||||
typeEncoders: new(typeEncoderCache),
|
||||
typeDecoders: new(typeDecoderCache),
|
||||
kindEncoders: new(kindEncoderCache),
|
||||
kindDecoders: new(kindDecoderCache),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +266,7 @@ func NewRegistry() *Registry {
|
||||
//
|
||||
// RegisterTypeEncoder should not be called concurrently with any other Registry method.
|
||||
func (r *Registry) RegisterTypeEncoder(valueType reflect.Type, enc ValueEncoder) {
|
||||
r.typeEncoders[valueType] = enc
|
||||
r.typeEncoders.Store(valueType, enc)
|
||||
}
|
||||
|
||||
// RegisterTypeDecoder registers the provided ValueDecoder for the provided type.
|
||||
@@ -310,7 +280,7 @@ func (r *Registry) RegisterTypeEncoder(valueType reflect.Type, enc ValueEncoder)
|
||||
//
|
||||
// RegisterTypeDecoder should not be called concurrently with any other Registry method.
|
||||
func (r *Registry) RegisterTypeDecoder(valueType reflect.Type, dec ValueDecoder) {
|
||||
r.typeDecoders[valueType] = dec
|
||||
r.typeDecoders.Store(valueType, dec)
|
||||
}
|
||||
|
||||
// RegisterKindEncoder registers the provided ValueEncoder for the provided kind.
|
||||
@@ -326,7 +296,7 @@ func (r *Registry) RegisterTypeDecoder(valueType reflect.Type, dec ValueDecoder)
|
||||
//
|
||||
// RegisterKindEncoder should not be called concurrently with any other Registry method.
|
||||
func (r *Registry) RegisterKindEncoder(kind reflect.Kind, enc ValueEncoder) {
|
||||
r.kindEncoders[kind] = enc
|
||||
r.kindEncoders.Store(kind, enc)
|
||||
}
|
||||
|
||||
// RegisterKindDecoder registers the provided ValueDecoder for the provided kind.
|
||||
@@ -342,7 +312,7 @@ func (r *Registry) RegisterKindEncoder(kind reflect.Kind, enc ValueEncoder) {
|
||||
//
|
||||
// RegisterKindDecoder should not be called concurrently with any other Registry method.
|
||||
func (r *Registry) RegisterKindDecoder(kind reflect.Kind, dec ValueDecoder) {
|
||||
r.kindDecoders[kind] = dec
|
||||
r.kindDecoders.Store(kind, dec)
|
||||
}
|
||||
|
||||
// RegisterInterfaceEncoder registers an encoder for the provided interface type iface. This encoder will
|
||||
@@ -401,7 +371,7 @@ func (r *Registry) RegisterInterfaceDecoder(iface reflect.Type, dec ValueDecoder
|
||||
//
|
||||
// reg.RegisterTypeMapEntry(bsontype.EmbeddedDocument, reflect.TypeOf(bson.Raw{}))
|
||||
func (r *Registry) RegisterTypeMapEntry(bt bsontype.Type, rt reflect.Type) {
|
||||
r.typeMap[bt] = rt
|
||||
r.typeMap.Store(bt, rt)
|
||||
}
|
||||
|
||||
// LookupEncoder returns the first matching encoder in the Registry. It uses the following lookup
|
||||
@@ -418,9 +388,10 @@ func (r *Registry) RegisterTypeMapEntry(bt bsontype.Type, rt reflect.Type) {
|
||||
// If no encoder is found, an error of type ErrNoEncoder is returned. LookupEncoder is safe for
|
||||
// concurrent use by multiple goroutines after all codecs and encoders are registered.
|
||||
func (r *Registry) LookupEncoder(valueType reflect.Type) (ValueEncoder, error) {
|
||||
r.mu.RLock()
|
||||
if valueType == nil {
|
||||
return nil, ErrNoEncoder{Type: valueType}
|
||||
}
|
||||
enc, found := r.lookupTypeEncoder(valueType)
|
||||
r.mu.RUnlock()
|
||||
if found {
|
||||
if enc == nil {
|
||||
return nil, ErrNoEncoder{Type: valueType}
|
||||
@@ -430,36 +401,21 @@ func (r *Registry) LookupEncoder(valueType reflect.Type) (ValueEncoder, error) {
|
||||
|
||||
enc, found = r.lookupInterfaceEncoder(valueType, true)
|
||||
if found {
|
||||
r.mu.Lock()
|
||||
r.typeEncoders[valueType] = enc
|
||||
r.mu.Unlock()
|
||||
return enc, nil
|
||||
return r.typeEncoders.LoadOrStore(valueType, enc), nil
|
||||
}
|
||||
|
||||
if valueType == nil {
|
||||
r.mu.Lock()
|
||||
r.typeEncoders[valueType] = nil
|
||||
r.mu.Unlock()
|
||||
return nil, ErrNoEncoder{Type: valueType}
|
||||
if v, ok := r.kindEncoders.Load(valueType.Kind()); ok {
|
||||
return r.storeTypeEncoder(valueType, v), nil
|
||||
}
|
||||
|
||||
enc, found = r.kindEncoders[valueType.Kind()]
|
||||
if !found {
|
||||
r.mu.Lock()
|
||||
r.typeEncoders[valueType] = nil
|
||||
r.mu.Unlock()
|
||||
return nil, ErrNoEncoder{Type: valueType}
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.typeEncoders[valueType] = enc
|
||||
r.mu.Unlock()
|
||||
return enc, nil
|
||||
return nil, ErrNoEncoder{Type: valueType}
|
||||
}
|
||||
|
||||
func (r *Registry) lookupTypeEncoder(valueType reflect.Type) (ValueEncoder, bool) {
|
||||
enc, found := r.typeEncoders[valueType]
|
||||
return enc, found
|
||||
func (r *Registry) storeTypeEncoder(rt reflect.Type, enc ValueEncoder) ValueEncoder {
|
||||
return r.typeEncoders.LoadOrStore(rt, enc)
|
||||
}
|
||||
|
||||
func (r *Registry) lookupTypeEncoder(rt reflect.Type) (ValueEncoder, bool) {
|
||||
return r.typeEncoders.Load(rt)
|
||||
}
|
||||
|
||||
func (r *Registry) lookupInterfaceEncoder(valueType reflect.Type, allowAddr bool) (ValueEncoder, bool) {
|
||||
@@ -475,7 +431,7 @@ func (r *Registry) lookupInterfaceEncoder(valueType reflect.Type, allowAddr bool
|
||||
// ahead in interfaceEncoders
|
||||
defaultEnc, found := r.lookupInterfaceEncoder(valueType, false)
|
||||
if !found {
|
||||
defaultEnc = r.kindEncoders[valueType.Kind()]
|
||||
defaultEnc, _ = r.kindEncoders.Load(valueType.Kind())
|
||||
}
|
||||
return newCondAddrEncoder(ienc.ve, defaultEnc), true
|
||||
}
|
||||
@@ -500,10 +456,7 @@ func (r *Registry) LookupDecoder(valueType reflect.Type) (ValueDecoder, error) {
|
||||
if valueType == nil {
|
||||
return nil, ErrNilType
|
||||
}
|
||||
decodererr := ErrNoDecoder{Type: valueType}
|
||||
r.mu.RLock()
|
||||
dec, found := r.lookupTypeDecoder(valueType)
|
||||
r.mu.RUnlock()
|
||||
if found {
|
||||
if dec == nil {
|
||||
return nil, ErrNoDecoder{Type: valueType}
|
||||
@@ -513,29 +466,21 @@ func (r *Registry) LookupDecoder(valueType reflect.Type) (ValueDecoder, error) {
|
||||
|
||||
dec, found = r.lookupInterfaceDecoder(valueType, true)
|
||||
if found {
|
||||
r.mu.Lock()
|
||||
r.typeDecoders[valueType] = dec
|
||||
r.mu.Unlock()
|
||||
return dec, nil
|
||||
return r.storeTypeDecoder(valueType, dec), nil
|
||||
}
|
||||
|
||||
dec, found = r.kindDecoders[valueType.Kind()]
|
||||
if !found {
|
||||
r.mu.Lock()
|
||||
r.typeDecoders[valueType] = nil
|
||||
r.mu.Unlock()
|
||||
return nil, decodererr
|
||||
if v, ok := r.kindDecoders.Load(valueType.Kind()); ok {
|
||||
return r.storeTypeDecoder(valueType, v), nil
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.typeDecoders[valueType] = dec
|
||||
r.mu.Unlock()
|
||||
return dec, nil
|
||||
return nil, ErrNoDecoder{Type: valueType}
|
||||
}
|
||||
|
||||
func (r *Registry) lookupTypeDecoder(valueType reflect.Type) (ValueDecoder, bool) {
|
||||
dec, found := r.typeDecoders[valueType]
|
||||
return dec, found
|
||||
return r.typeDecoders.Load(valueType)
|
||||
}
|
||||
|
||||
func (r *Registry) storeTypeDecoder(typ reflect.Type, dec ValueDecoder) ValueDecoder {
|
||||
return r.typeDecoders.LoadOrStore(typ, dec)
|
||||
}
|
||||
|
||||
func (r *Registry) lookupInterfaceDecoder(valueType reflect.Type, allowAddr bool) (ValueDecoder, bool) {
|
||||
@@ -548,7 +493,7 @@ func (r *Registry) lookupInterfaceDecoder(valueType reflect.Type, allowAddr bool
|
||||
// ahead in interfaceDecoders
|
||||
defaultDec, found := r.lookupInterfaceDecoder(valueType, false)
|
||||
if !found {
|
||||
defaultDec = r.kindDecoders[valueType.Kind()]
|
||||
defaultDec, _ = r.kindDecoders.Load(valueType.Kind())
|
||||
}
|
||||
return newCondAddrDecoder(idec.vd, defaultDec), true
|
||||
}
|
||||
@@ -561,11 +506,11 @@ func (r *Registry) lookupInterfaceDecoder(valueType reflect.Type, allowAddr bool
|
||||
//
|
||||
// LookupTypeMapEntry should not be called concurrently with any other Registry method.
|
||||
func (r *Registry) LookupTypeMapEntry(bt bsontype.Type) (reflect.Type, error) {
|
||||
t, ok := r.typeMap[bt]
|
||||
if !ok || t == nil {
|
||||
v, ok := r.typeMap.Load(bt)
|
||||
if v == nil || !ok {
|
||||
return nil, ErrNoTypeMapEntry{Type: bt}
|
||||
}
|
||||
return t, nil
|
||||
return v.(reflect.Type), nil
|
||||
}
|
||||
|
||||
type interfaceValueEncoder struct {
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ func (sc SliceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val re
|
||||
}
|
||||
|
||||
// If we have a []primitive.E we want to treat it as a document instead of as an array.
|
||||
if val.Type().ConvertibleTo(tD) {
|
||||
if val.Type() == tD || val.Type().ConvertibleTo(tD) {
|
||||
d := val.Convert(tD).Interface().(primitive.D)
|
||||
|
||||
dw, err := vw.WriteDocument()
|
||||
|
||||
+44
-61
@@ -63,8 +63,7 @@ type Zeroer interface {
|
||||
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with the
|
||||
// StructCodec registered.
|
||||
type StructCodec struct {
|
||||
cache map[reflect.Type]*structDescription
|
||||
l sync.RWMutex
|
||||
cache sync.Map // map[reflect.Type]*structDescription
|
||||
parser StructTagParser
|
||||
|
||||
// DecodeZeroStruct causes DecodeValue to delete any existing values from Go structs in the
|
||||
@@ -115,7 +114,6 @@ func NewStructCodec(p StructTagParser, opts ...*bsonoptions.StructCodecOptions)
|
||||
structOpt := bsonoptions.MergeStructCodecOptions(opts...)
|
||||
|
||||
codec := &StructCodec{
|
||||
cache: make(map[reflect.Type]*structDescription),
|
||||
parser: p,
|
||||
}
|
||||
|
||||
@@ -192,15 +190,14 @@ func (sc *StructCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val
|
||||
encoder := desc.encoder
|
||||
|
||||
var zero bool
|
||||
rvInterface := rv.Interface()
|
||||
if cz, ok := encoder.(CodecZeroer); ok {
|
||||
zero = cz.IsTypeZero(rvInterface)
|
||||
zero = cz.IsTypeZero(rv.Interface())
|
||||
} else if rv.Kind() == reflect.Interface {
|
||||
// isZero will not treat an interface rv as an interface, so we need to check for the
|
||||
// zero interface separately.
|
||||
zero = rv.IsNil()
|
||||
} else {
|
||||
zero = isZero(rvInterface, sc.EncodeOmitDefaultStruct || ec.omitZeroStruct)
|
||||
zero = isZero(rv, sc.EncodeOmitDefaultStruct || ec.omitZeroStruct)
|
||||
}
|
||||
if desc.omitEmpty && zero {
|
||||
continue
|
||||
@@ -394,56 +391,32 @@ func (sc *StructCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val
|
||||
return nil
|
||||
}
|
||||
|
||||
func isZero(i interface{}, omitZeroStruct bool) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
|
||||
// check the value validity
|
||||
if !v.IsValid() {
|
||||
return true
|
||||
func isZero(v reflect.Value, omitZeroStruct bool) bool {
|
||||
kind := v.Kind()
|
||||
if (kind != reflect.Ptr || !v.IsNil()) && v.Type().Implements(tZeroer) {
|
||||
return v.Interface().(Zeroer).IsZero()
|
||||
}
|
||||
|
||||
if z, ok := v.Interface().(Zeroer); ok && (v.Kind() != reflect.Ptr || !v.IsNil()) {
|
||||
return z.IsZero()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Struct:
|
||||
if kind == reflect.Struct {
|
||||
if !omitZeroStruct {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(GODRIVER-2820): Update the logic to be able to handle private struct fields.
|
||||
// TODO Use condition "reflect.Zero(v.Type()).Equal(v)" instead.
|
||||
|
||||
vt := v.Type()
|
||||
if vt == tTime {
|
||||
return v.Interface().(time.Time).IsZero()
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if vt.Field(i).PkgPath != "" && !vt.Field(i).Anonymous {
|
||||
numField := vt.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
ff := vt.Field(i)
|
||||
if ff.PkgPath != "" && !ff.Anonymous {
|
||||
continue // Private field
|
||||
}
|
||||
fld := v.Field(i)
|
||||
if !isZero(fld.Interface(), omitZeroStruct) {
|
||||
if !isZero(v.Field(i), omitZeroStruct) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return !v.IsValid() || v.IsZero()
|
||||
}
|
||||
|
||||
type structDescription struct {
|
||||
@@ -502,13 +475,27 @@ func (sc *StructCodec) describeStruct(
|
||||
) (*structDescription, error) {
|
||||
// We need to analyze the struct, including getting the tags, collecting
|
||||
// information about inlining, and create a map of the field name to the field.
|
||||
sc.l.RLock()
|
||||
ds, exists := sc.cache[t]
|
||||
sc.l.RUnlock()
|
||||
if exists {
|
||||
return ds, nil
|
||||
if v, ok := sc.cache.Load(t); ok {
|
||||
return v.(*structDescription), nil
|
||||
}
|
||||
// TODO(charlie): Only describe the struct once when called
|
||||
// concurrently with the same type.
|
||||
ds, err := sc.describeStructSlow(r, t, useJSONStructTags, errorOnDuplicates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v, loaded := sc.cache.LoadOrStore(t, ds); loaded {
|
||||
ds = v.(*structDescription)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func (sc *StructCodec) describeStructSlow(
|
||||
r *Registry,
|
||||
t reflect.Type,
|
||||
useJSONStructTags bool,
|
||||
errorOnDuplicates bool,
|
||||
) (*structDescription, error) {
|
||||
numFields := t.NumField()
|
||||
sd := &structDescription{
|
||||
fm: make(map[string]fieldDescription, numFields),
|
||||
@@ -639,10 +626,6 @@ func (sc *StructCodec) describeStruct(
|
||||
|
||||
sort.Sort(byIndex(sd.fl))
|
||||
|
||||
sc.l.Lock()
|
||||
sc.cache[t] = sd
|
||||
sc.l.Unlock()
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
@@ -700,21 +683,21 @@ func getInlineField(val reflect.Value, index []int) (reflect.Value, error) {
|
||||
|
||||
// DeepZero returns recursive zero object
|
||||
func deepZero(st reflect.Type) (result reflect.Value) {
|
||||
result = reflect.Indirect(reflect.New(st))
|
||||
|
||||
if result.Kind() == reflect.Struct {
|
||||
for i := 0; i < result.NumField(); i++ {
|
||||
if f := result.Field(i); f.Kind() == reflect.Ptr {
|
||||
if f.CanInterface() {
|
||||
if ft := reflect.TypeOf(f.Interface()); ft.Elem().Kind() == reflect.Struct {
|
||||
result.Field(i).Set(recursivePointerTo(deepZero(ft.Elem())))
|
||||
}
|
||||
if st.Kind() == reflect.Struct {
|
||||
numField := st.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
if result == emptyValue {
|
||||
result = reflect.Indirect(reflect.New(st))
|
||||
}
|
||||
f := result.Field(i)
|
||||
if f.CanInterface() {
|
||||
if f.Type().Kind() == reflect.Struct {
|
||||
result.Field(i).Set(recursivePointerTo(deepZero(f.Type().Elem())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
||||
// recursivePointerTo calls reflect.New(v.Type) but recursively for its fields inside
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ var tValueUnmarshaler = reflect.TypeOf((*ValueUnmarshaler)(nil)).Elem()
|
||||
var tMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
var tUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
var tProxy = reflect.TypeOf((*Proxy)(nil)).Elem()
|
||||
var tZeroer = reflect.TypeOf((*Zeroer)(nil)).Elem()
|
||||
|
||||
var tBinary = reflect.TypeOf(primitive.Binary{})
|
||||
var tUndefined = reflect.TypeOf(primitive.Undefined{})
|
||||
|
||||
+4
-4
@@ -124,7 +124,7 @@ func (c Copier) CopyBytesToDocumentWriter(dst DocumentWriter, src []byte) error
|
||||
}
|
||||
|
||||
func (c Copier) copyBytesToValueWriter(src []byte, wef writeElementFn) error {
|
||||
// TODO(skriptble): Create errors types here. Anything thats a tag should be a property.
|
||||
// TODO(skriptble): Create errors types here. Anything that is a tag should be a property.
|
||||
length, rem, ok := bsoncore.ReadLength(src)
|
||||
if !ok {
|
||||
return fmt.Errorf("couldn't read length from src, not enough bytes. length=%d", len(src))
|
||||
@@ -193,7 +193,7 @@ func (c Copier) AppendDocumentBytes(dst []byte, src ValueReader) ([]byte, error)
|
||||
}
|
||||
|
||||
vw := vwPool.Get().(*valueWriter)
|
||||
defer vwPool.Put(vw)
|
||||
defer putValueWriter(vw)
|
||||
|
||||
vw.reset(dst)
|
||||
|
||||
@@ -213,7 +213,7 @@ func (c Copier) AppendArrayBytes(dst []byte, src ValueReader) ([]byte, error) {
|
||||
}
|
||||
|
||||
vw := vwPool.Get().(*valueWriter)
|
||||
defer vwPool.Put(vw)
|
||||
defer putValueWriter(vw)
|
||||
|
||||
vw.reset(dst)
|
||||
|
||||
@@ -258,7 +258,7 @@ func (c Copier) AppendValueBytes(dst []byte, src ValueReader) (bsontype.Type, []
|
||||
}
|
||||
|
||||
vw := vwPool.Get().(*valueWriter)
|
||||
defer vwPool.Put(vw)
|
||||
defer putValueWriter(vw)
|
||||
|
||||
start := len(dst)
|
||||
|
||||
|
||||
+10
-2
@@ -739,8 +739,7 @@ func (vr *valueReader) ReadValue() (ValueReader, error) {
|
||||
return nil, ErrEOA
|
||||
}
|
||||
|
||||
_, err = vr.readCString()
|
||||
if err != nil {
|
||||
if err := vr.skipCString(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -794,6 +793,15 @@ func (vr *valueReader) readByte() (byte, error) {
|
||||
return vr.d[vr.offset-1], nil
|
||||
}
|
||||
|
||||
func (vr *valueReader) skipCString() error {
|
||||
idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
|
||||
if idx < 0 {
|
||||
return io.EOF
|
||||
}
|
||||
vr.offset += int64(idx) + 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *valueReader) readCString() (string, error) {
|
||||
idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
|
||||
if idx < 0 {
|
||||
|
||||
+49
-25
@@ -28,6 +28,13 @@ var vwPool = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
func putValueWriter(vw *valueWriter) {
|
||||
if vw != nil {
|
||||
vw.w = nil // don't leak the writer
|
||||
vwPool.Put(vw)
|
||||
}
|
||||
}
|
||||
|
||||
// BSONValueWriterPool is a pool for BSON ValueWriters.
|
||||
//
|
||||
// Deprecated: BSONValueWriterPool will not be supported in Go Driver 2.0.
|
||||
@@ -149,32 +156,21 @@ type valueWriter struct {
|
||||
}
|
||||
|
||||
func (vw *valueWriter) advanceFrame() {
|
||||
if vw.frame+1 >= int64(len(vw.stack)) { // We need to grow the stack
|
||||
length := len(vw.stack)
|
||||
if length+1 >= cap(vw.stack) {
|
||||
// double it
|
||||
buf := make([]vwState, 2*cap(vw.stack)+1)
|
||||
copy(buf, vw.stack)
|
||||
vw.stack = buf
|
||||
}
|
||||
vw.stack = vw.stack[:length+1]
|
||||
}
|
||||
vw.frame++
|
||||
if vw.frame >= int64(len(vw.stack)) {
|
||||
vw.stack = append(vw.stack, vwState{})
|
||||
}
|
||||
}
|
||||
|
||||
func (vw *valueWriter) push(m mode) {
|
||||
vw.advanceFrame()
|
||||
|
||||
// Clean the stack
|
||||
vw.stack[vw.frame].mode = m
|
||||
vw.stack[vw.frame].key = ""
|
||||
vw.stack[vw.frame].arrkey = 0
|
||||
vw.stack[vw.frame].start = 0
|
||||
vw.stack[vw.frame] = vwState{mode: m}
|
||||
|
||||
vw.stack[vw.frame].mode = m
|
||||
switch m {
|
||||
case mDocument, mArray, mCodeWithScope:
|
||||
vw.reserveLength()
|
||||
vw.reserveLength() // WARN: this is not needed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +209,7 @@ func newValueWriter(w io.Writer) *valueWriter {
|
||||
return vw
|
||||
}
|
||||
|
||||
// TODO: only used in tests
|
||||
func newValueWriterFromSlice(buf []byte) *valueWriter {
|
||||
vw := new(valueWriter)
|
||||
stack := make([]vwState, 1, 5)
|
||||
@@ -249,17 +246,16 @@ func (vw *valueWriter) invalidTransitionError(destination mode, name string, mod
|
||||
}
|
||||
|
||||
func (vw *valueWriter) writeElementHeader(t bsontype.Type, destination mode, callerName string, addmodes ...mode) error {
|
||||
switch vw.stack[vw.frame].mode {
|
||||
frame := &vw.stack[vw.frame]
|
||||
switch frame.mode {
|
||||
case mElement:
|
||||
key := vw.stack[vw.frame].key
|
||||
key := frame.key
|
||||
if !isValidCString(key) {
|
||||
return errors.New("BSON element key cannot contain null bytes")
|
||||
}
|
||||
|
||||
vw.buf = bsoncore.AppendHeader(vw.buf, t, key)
|
||||
vw.appendHeader(t, key)
|
||||
case mValue:
|
||||
// TODO: Do this with a cache of the first 1000 or so array keys.
|
||||
vw.buf = bsoncore.AppendHeader(vw.buf, t, strconv.Itoa(vw.stack[vw.frame].arrkey))
|
||||
vw.appendIntHeader(t, frame.arrkey)
|
||||
default:
|
||||
modes := []mode{mElement, mValue}
|
||||
if addmodes != nil {
|
||||
@@ -601,9 +597,11 @@ func (vw *valueWriter) writeLength() error {
|
||||
if length > maxSize {
|
||||
return errMaxDocumentSizeExceeded{size: int64(len(vw.buf))}
|
||||
}
|
||||
length = length - int(vw.stack[vw.frame].start)
|
||||
start := vw.stack[vw.frame].start
|
||||
frame := &vw.stack[vw.frame]
|
||||
length = length - int(frame.start)
|
||||
start := frame.start
|
||||
|
||||
_ = vw.buf[start+3] // BCE
|
||||
vw.buf[start+0] = byte(length)
|
||||
vw.buf[start+1] = byte(length >> 8)
|
||||
vw.buf[start+2] = byte(length >> 16)
|
||||
@@ -612,5 +610,31 @@ func (vw *valueWriter) writeLength() error {
|
||||
}
|
||||
|
||||
func isValidCString(cs string) bool {
|
||||
return !strings.ContainsRune(cs, '\x00')
|
||||
// Disallow the zero byte in a cstring because the zero byte is used as the
|
||||
// terminating character.
|
||||
//
|
||||
// It's safe to check bytes instead of runes because all multibyte UTF-8
|
||||
// code points start with (binary) 11xxxxxx or 10xxxxxx, so 00000000 (i.e.
|
||||
// 0) will never be part of a multibyte UTF-8 code point. This logic is the
|
||||
// same as the "r < utf8.RuneSelf" case in strings.IndexRune but can be
|
||||
// inlined.
|
||||
//
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/strings/strings.go;l=127
|
||||
return strings.IndexByte(cs, 0) == -1
|
||||
}
|
||||
|
||||
// appendHeader is the same as bsoncore.AppendHeader but does not check if the
|
||||
// key is a valid C string since the caller has already checked for that.
|
||||
//
|
||||
// The caller of this function must check if key is a valid C string.
|
||||
func (vw *valueWriter) appendHeader(t bsontype.Type, key string) {
|
||||
vw.buf = bsoncore.AppendType(vw.buf, t)
|
||||
vw.buf = append(vw.buf, key...)
|
||||
vw.buf = append(vw.buf, 0x00)
|
||||
}
|
||||
|
||||
func (vw *valueWriter) appendIntHeader(t bsontype.Type, key int) {
|
||||
vw.buf = bsoncore.AppendType(vw.buf, t)
|
||||
vw.buf = strconv.AppendInt(vw.buf, int64(key), 10)
|
||||
vw.buf = append(vw.buf, 0x00)
|
||||
}
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ const (
|
||||
BinaryMD5 byte = 0x05
|
||||
BinaryEncrypted byte = 0x06
|
||||
BinaryColumn byte = 0x07
|
||||
BinarySensitive byte = 0x08
|
||||
BinaryUserDefined byte = 0x80
|
||||
)
|
||||
|
||||
|
||||
+29
-3
@@ -9,6 +9,7 @@ package bson
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||
@@ -141,6 +142,13 @@ func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{
|
||||
return MarshalAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val)
|
||||
}
|
||||
|
||||
// Pool of buffers for marshalling BSON.
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
// MarshalAppendWithContext will encode val as a BSON document using Registry r and EncodeContext ec and append the
|
||||
// bytes to dst. If dst is not large enough to hold the bytes, it will be grown. If val is not a type that can be
|
||||
// transformed into a document, MarshalValueAppendWithContext should be used instead.
|
||||
@@ -162,8 +170,26 @@ func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{
|
||||
//
|
||||
// See [Encoder] for more examples.
|
||||
func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}) ([]byte, error) {
|
||||
sw := new(bsonrw.SliceWriter)
|
||||
*sw = dst
|
||||
sw := bufPool.Get().(*bytes.Buffer)
|
||||
defer func() {
|
||||
// Proper usage of a sync.Pool requires each entry to have approximately
|
||||
// the same memory cost. To obtain this property when the stored type
|
||||
// contains a variably-sized buffer, we add a hard limit on the maximum
|
||||
// buffer to place back in the pool. We limit the size to 16MiB because
|
||||
// that's the maximum wire message size supported by any current MongoDB
|
||||
// server.
|
||||
//
|
||||
// Comment based on
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/fmt/print.go;l=147
|
||||
//
|
||||
// Recycle byte slices that are smaller than 16MiB and at least half
|
||||
// occupied.
|
||||
if sw.Cap() < 16*1024*1024 && sw.Cap()/2 < sw.Len() {
|
||||
bufPool.Put(sw)
|
||||
}
|
||||
}()
|
||||
|
||||
sw.Reset()
|
||||
vw := bvwPool.Get(sw)
|
||||
defer bvwPool.Put(vw)
|
||||
|
||||
@@ -184,7 +210,7 @@ func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interf
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return *sw, nil
|
||||
return append(dst, sw.Bytes()...), nil
|
||||
}
|
||||
|
||||
// MarshalValue returns the BSON encoding of val.
|
||||
|
||||
+9
-2
@@ -60,12 +60,19 @@ func (r Raw) LookupErr(key ...string) (RawValue, error) {
|
||||
// elements. If the document is not valid, the elements up to the invalid point will be returned
|
||||
// along with an error.
|
||||
func (r Raw) Elements() ([]RawElement, error) {
|
||||
elems, err := bsoncore.Document(r).Elements()
|
||||
doc := bsoncore.Document(r)
|
||||
if len(doc) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
elems, err := doc.Elements()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relems := make([]RawElement, 0, len(elems))
|
||||
for _, elem := range elems {
|
||||
relems = append(relems, RawElement(elem))
|
||||
}
|
||||
return relems, err
|
||||
return relems, nil
|
||||
}
|
||||
|
||||
// Values returns this document as a slice of values. The returned slice will contain valid values.
|
||||
|
||||
+1
@@ -45,5 +45,6 @@ const (
|
||||
TypeBinaryMD5 = bsontype.BinaryMD5
|
||||
TypeBinaryEncrypted = bsontype.BinaryEncrypted
|
||||
TypeBinaryColumn = bsontype.BinaryColumn
|
||||
TypeBinarySensitive = bsontype.BinarySensitive
|
||||
TypeBinaryUserDefined = bsontype.BinaryUserDefined
|
||||
)
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ type CommandFinishedEvent struct {
|
||||
DurationNanos int64
|
||||
Duration time.Duration
|
||||
CommandName string
|
||||
DatabaseName string
|
||||
RequestID int64
|
||||
ConnectionID string
|
||||
// ServerConnectionID contains the connection ID from the server of the operation. If the server does not return
|
||||
|
||||
Generated
Vendored
+25
-8
@@ -4,7 +4,7 @@
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal
|
||||
package bsonutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -12,13 +12,6 @@ import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// StringSliceFromRawElement decodes the provided BSON element into a []string. This internally calls
|
||||
// StringSliceFromRawValue on the element's value. The error conditions outlined in that function's documentation
|
||||
// apply for this function as well.
|
||||
func StringSliceFromRawElement(element bson.RawElement) ([]string, error) {
|
||||
return StringSliceFromRawValue(element.Key(), element.Value())
|
||||
}
|
||||
|
||||
// StringSliceFromRawValue decodes the provided BSON value into a []string. This function returns an error if the value
|
||||
// is not an array or any of the elements in the array are not strings. The name parameter is used to add context to
|
||||
// error messages.
|
||||
@@ -43,3 +36,27 @@ func StringSliceFromRawValue(name string, val bson.RawValue) ([]string, error) {
|
||||
}
|
||||
return strs, nil
|
||||
}
|
||||
|
||||
// RawToDocuments converts a bson.Raw that is internally an array of documents to []bson.Raw.
|
||||
func RawToDocuments(doc bson.Raw) []bson.Raw {
|
||||
values, err := doc.Values()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error converting BSON document to values: %v", err))
|
||||
}
|
||||
|
||||
out := make([]bson.Raw, len(values))
|
||||
for i := range values {
|
||||
out[i] = values[i].Document()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// RawToInterfaces takes one or many bson.Raw documents and returns them as a []interface{}.
|
||||
func RawToInterfaces(docs ...bson.Raw) []interface{} {
|
||||
out := make([]interface{}, len(docs))
|
||||
for i := range docs {
|
||||
out[i] = docs[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import "context"
|
||||
|
||||
// CancellationListener listens for context cancellation in a loop until the context expires or the listener is aborted.
|
||||
type CancellationListener struct {
|
||||
aborted bool
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewCancellationListener constructs a CancellationListener.
|
||||
func NewCancellationListener() *CancellationListener {
|
||||
return &CancellationListener{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Listen blocks until the provided context is cancelled or listening is aborted via the StopListening function. If this
|
||||
// detects that the context has been cancelled (i.e. ctx.Err() == context.Canceled), the provided callback is called to
|
||||
// abort in-progress work. Even if the context expires, this function will block until StopListening is called.
|
||||
func (c *CancellationListener) Listen(ctx context.Context, abortFn func()) {
|
||||
c.aborted = false
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() == context.Canceled {
|
||||
c.aborted = true
|
||||
abortFn()
|
||||
}
|
||||
|
||||
<-c.done
|
||||
case <-c.done:
|
||||
}
|
||||
}
|
||||
|
||||
// StopListening stops the in-progress Listen call. This blocks if there is no in-progress Listen call. This function
|
||||
// will return true if the provided abort callback was called when listening for cancellation on the previous context.
|
||||
func (c *CancellationListener) StopListening() bool {
|
||||
c.done <- struct{}{}
|
||||
return c.aborted
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) MongoDB, Inc. 2023-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package codecutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||||
)
|
||||
|
||||
var ErrNilValue = errors.New("value is nil")
|
||||
|
||||
// MarshalError is returned when attempting to transform a value into a document
|
||||
// results in an error.
|
||||
type MarshalError struct {
|
||||
Value interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e MarshalError) Error() string {
|
||||
return fmt.Sprintf("cannot transform type %s to a BSON Document: %v",
|
||||
reflect.TypeOf(e.Value), e.Err)
|
||||
}
|
||||
|
||||
// EncoderFn is used to functionally construct an encoder for marshaling values.
|
||||
type EncoderFn func(io.Writer) (*bson.Encoder, error)
|
||||
|
||||
// MarshalValue will attempt to encode the value with the encoder returned by
|
||||
// the encoder function.
|
||||
func MarshalValue(val interface{}, encFn EncoderFn) (bsoncore.Value, error) {
|
||||
// If the val is already a bsoncore.Value, then do nothing.
|
||||
if bval, ok := val.(bsoncore.Value); ok {
|
||||
return bval, nil
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
return bsoncore.Value{}, ErrNilValue
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
enc, err := encFn(buf)
|
||||
if err != nil {
|
||||
return bsoncore.Value{}, err
|
||||
}
|
||||
|
||||
// Encode the value in a single-element document with an empty key. Use
|
||||
// bsoncore to extract the first element and return the BSON value.
|
||||
err = enc.Encode(bson.D{{Key: "", Value: val}})
|
||||
if err != nil {
|
||||
return bsoncore.Value{}, MarshalError{Value: val, Err: err}
|
||||
}
|
||||
|
||||
return bsoncore.Document(buf.Bytes()).Index(0).Value(), nil
|
||||
}
|
||||
Generated
Vendored
+5
-6
@@ -15,7 +15,6 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/internal"
|
||||
"go.mongodb.org/mongo-driver/internal/aws/credentials"
|
||||
)
|
||||
|
||||
@@ -47,7 +46,7 @@ func (a *AzureProvider) RetrieveWithContext(ctx context.Context) (credentials.Va
|
||||
v := credentials.Value{ProviderName: AzureProviderName}
|
||||
req, err := http.NewRequest(http.MethodGet, azureURI, nil)
|
||||
if err != nil {
|
||||
return v, internal.WrapErrorf(err, "unable to retrieve Azure credentials")
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: %w", err)
|
||||
}
|
||||
q := make(url.Values)
|
||||
q.Set("api-version", "2018-02-01")
|
||||
@@ -58,15 +57,15 @@ func (a *AzureProvider) RetrieveWithContext(ctx context.Context) (credentials.Va
|
||||
|
||||
resp, err := a.httpClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return v, internal.WrapErrorf(err, "unable to retrieve Azure credentials")
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return v, internal.WrapErrorf(err, "unable to retrieve Azure credentials: error reading response body")
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: error reading response body: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return v, internal.WrapErrorf(err, "unable to retrieve Azure credentials: expected StatusCode 200, got StatusCode: %v. Response body: %s", resp.StatusCode, body)
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: expected StatusCode 200, got StatusCode: %v. Response body: %s", resp.StatusCode, body)
|
||||
}
|
||||
var tokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
@@ -75,7 +74,7 @@ func (a *AzureProvider) RetrieveWithContext(ctx context.Context) (credentials.Va
|
||||
// Attempt to read body as JSON
|
||||
err = json.Unmarshal(body, &tokenResponse)
|
||||
if err != nil {
|
||||
return v, internal.WrapErrorf(err, "unable to retrieve Azure credentials: error reading body JSON. Response body: %s", body)
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: error reading body JSON: %w (response body: %s)", err, body)
|
||||
}
|
||||
if tokenResponse.AccessToken == "" {
|
||||
return v, fmt.Errorf("unable to retrieve Azure credentials: got unexpected empty accessToken from Azure Metadata Server. Response body: %s", body)
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal
|
||||
package csfle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
Generated
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal
|
||||
package csot
|
||||
|
||||
import (
|
||||
"context"
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright (C) MongoDB, Inc. 2023-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package driverutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const AwsLambdaPrefix = "AWS_Lambda_"
|
||||
|
||||
const (
|
||||
// FaaS environment variable names
|
||||
|
||||
// EnvVarAWSExecutionEnv is the AWS Execution environment variable.
|
||||
EnvVarAWSExecutionEnv = "AWS_EXECUTION_ENV"
|
||||
// EnvVarAWSLambdaRuntimeAPI is the AWS Lambda runtime API variable.
|
||||
EnvVarAWSLambdaRuntimeAPI = "AWS_LAMBDA_RUNTIME_API"
|
||||
// EnvVarFunctionsWorkerRuntime is the functions worker runtime variable.
|
||||
EnvVarFunctionsWorkerRuntime = "FUNCTIONS_WORKER_RUNTIME"
|
||||
// EnvVarKService is the K Service variable.
|
||||
EnvVarKService = "K_SERVICE"
|
||||
// EnvVarFunctionName is the function name variable.
|
||||
EnvVarFunctionName = "FUNCTION_NAME"
|
||||
// EnvVarVercel is the Vercel variable.
|
||||
EnvVarVercel = "VERCEL"
|
||||
// EnvVarK8s is the K8s variable.
|
||||
EnvVarK8s = "KUBERNETES_SERVICE_HOST"
|
||||
)
|
||||
|
||||
const (
|
||||
// FaaS environment variable names
|
||||
|
||||
// EnvVarAWSRegion is the AWS region variable.
|
||||
EnvVarAWSRegion = "AWS_REGION"
|
||||
// EnvVarAWSLambdaFunctionMemorySize is the AWS Lambda function memory size variable.
|
||||
EnvVarAWSLambdaFunctionMemorySize = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"
|
||||
// EnvVarFunctionMemoryMB is the function memory in megabytes variable.
|
||||
EnvVarFunctionMemoryMB = "FUNCTION_MEMORY_MB"
|
||||
// EnvVarFunctionTimeoutSec is the function timeout in seconds variable.
|
||||
EnvVarFunctionTimeoutSec = "FUNCTION_TIMEOUT_SEC"
|
||||
// EnvVarFunctionRegion is the function region variable.
|
||||
EnvVarFunctionRegion = "FUNCTION_REGION"
|
||||
// EnvVarVercelRegion is the Vercel region variable.
|
||||
EnvVarVercelRegion = "VERCEL_REGION"
|
||||
)
|
||||
|
||||
const (
|
||||
// FaaS environment names used by the client
|
||||
|
||||
// EnvNameAWSLambda is the AWS Lambda environment name.
|
||||
EnvNameAWSLambda = "aws.lambda"
|
||||
// EnvNameAzureFunc is the Azure Function environment name.
|
||||
EnvNameAzureFunc = "azure.func"
|
||||
// EnvNameGCPFunc is the Google Cloud Function environment name.
|
||||
EnvNameGCPFunc = "gcp.func"
|
||||
// EnvNameVercel is the Vercel environment name.
|
||||
EnvNameVercel = "vercel"
|
||||
)
|
||||
|
||||
// GetFaasEnvName parses the FaaS environment variable name and returns the
|
||||
// corresponding name used by the client. If none of the variables or variables
|
||||
// for multiple names are populated the client.env value MUST be entirely
|
||||
// omitted. When variables for multiple "client.env.name" values are present,
|
||||
// "vercel" takes precedence over "aws.lambda"; any other combination MUST cause
|
||||
// "client.env" to be entirely omitted.
|
||||
func GetFaasEnvName() string {
|
||||
envVars := []string{
|
||||
EnvVarAWSExecutionEnv,
|
||||
EnvVarAWSLambdaRuntimeAPI,
|
||||
EnvVarFunctionsWorkerRuntime,
|
||||
EnvVarKService,
|
||||
EnvVarFunctionName,
|
||||
EnvVarVercel,
|
||||
}
|
||||
|
||||
// If none of the variables are populated the client.env value MUST be
|
||||
// entirely omitted.
|
||||
names := make(map[string]struct{})
|
||||
|
||||
for _, envVar := range envVars {
|
||||
val := os.Getenv(envVar)
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var name string
|
||||
|
||||
switch envVar {
|
||||
case EnvVarAWSExecutionEnv:
|
||||
if !strings.HasPrefix(val, AwsLambdaPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
name = EnvNameAWSLambda
|
||||
case EnvVarAWSLambdaRuntimeAPI:
|
||||
name = EnvNameAWSLambda
|
||||
case EnvVarFunctionsWorkerRuntime:
|
||||
name = EnvNameAzureFunc
|
||||
case EnvVarKService, EnvVarFunctionName:
|
||||
name = EnvNameGCPFunc
|
||||
case EnvVarVercel:
|
||||
// "vercel" takes precedence over "aws.lambda".
|
||||
delete(names, EnvNameAWSLambda)
|
||||
|
||||
name = EnvNameVercel
|
||||
}
|
||||
|
||||
names[name] = struct{}{}
|
||||
if len(names) > 1 {
|
||||
// If multiple names are populated the client.env value
|
||||
// MUST be entirely omitted.
|
||||
names = nil
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for name := range names {
|
||||
return name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright (C) MongoDB, Inc. 2023-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package driverutil
|
||||
|
||||
// Operation Names should be sourced from the command reference documentation:
|
||||
// https://www.mongodb.com/docs/manual/reference/command/
|
||||
const (
|
||||
AbortTransactionOp = "abortTransaction" // AbortTransactionOp is the name for aborting a transaction
|
||||
AggregateOp = "aggregate" // AggregateOp is the name for aggregating
|
||||
CommitTransactionOp = "commitTransaction" // CommitTransactionOp is the name for committing a transaction
|
||||
CountOp = "count" // CountOp is the name for counting
|
||||
CreateOp = "create" // CreateOp is the name for creating
|
||||
CreateIndexesOp = "createIndexes" // CreateIndexesOp is the name for creating indexes
|
||||
DeleteOp = "delete" // DeleteOp is the name for deleting
|
||||
DistinctOp = "distinct" // DistinctOp is the name for distinct
|
||||
DropOp = "drop" // DropOp is the name for dropping
|
||||
DropDatabaseOp = "dropDatabase" // DropDatabaseOp is the name for dropping a database
|
||||
DropIndexesOp = "dropIndexes" // DropIndexesOp is the name for dropping indexes
|
||||
EndSessionsOp = "endSessions" // EndSessionsOp is the name for ending sessions
|
||||
FindAndModifyOp = "findAndModify" // FindAndModifyOp is the name for finding and modifying
|
||||
FindOp = "find" // FindOp is the name for finding
|
||||
InsertOp = "insert" // InsertOp is the name for inserting
|
||||
ListCollectionsOp = "listCollections" // ListCollectionsOp is the name for listing collections
|
||||
ListIndexesOp = "listIndexes" // ListIndexesOp is the name for listing indexes
|
||||
ListDatabasesOp = "listDatabases" // ListDatabasesOp is the name for listing databases
|
||||
UpdateOp = "update" // UpdateOp is the name for updating
|
||||
)
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// WrappedError represents an error that contains another error.
|
||||
type WrappedError interface {
|
||||
// Message gets the basic message of the error.
|
||||
Message() string
|
||||
// Inner gets the inner error if one exists.
|
||||
Inner() error
|
||||
}
|
||||
|
||||
// RolledUpErrorMessage gets a flattened error message.
|
||||
func RolledUpErrorMessage(err error) string {
|
||||
if wrappedErr, ok := err.(WrappedError); ok {
|
||||
inner := wrappedErr.Inner()
|
||||
if inner != nil {
|
||||
return fmt.Sprintf("%s: %s", wrappedErr.Message(), RolledUpErrorMessage(inner))
|
||||
}
|
||||
|
||||
return wrappedErr.Message()
|
||||
}
|
||||
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// UnwrapError attempts to unwrap the error down to its root cause.
|
||||
func UnwrapError(err error) error {
|
||||
|
||||
switch tErr := err.(type) {
|
||||
case WrappedError:
|
||||
return UnwrapError(tErr.Inner())
|
||||
case *multiError:
|
||||
return UnwrapError(tErr.errors[0])
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// WrapError wraps an error with a message.
|
||||
func WrapError(inner error, message string) error {
|
||||
return &wrappedError{message, inner}
|
||||
}
|
||||
|
||||
// WrapErrorf wraps an error with a message.
|
||||
func WrapErrorf(inner error, format string, args ...interface{}) error {
|
||||
return &wrappedError{fmt.Sprintf(format, args...), inner}
|
||||
}
|
||||
|
||||
// MultiError combines multiple errors into a single error. If there are no errors,
|
||||
// nil is returned. If there is 1 error, it is returned. Otherwise, they are combined.
|
||||
func MultiError(errors ...error) error {
|
||||
|
||||
// remove nils from the error list
|
||||
var nonNils []error
|
||||
for _, e := range errors {
|
||||
if e != nil {
|
||||
nonNils = append(nonNils, e)
|
||||
}
|
||||
}
|
||||
|
||||
switch len(nonNils) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return nonNils[0]
|
||||
default:
|
||||
return &multiError{
|
||||
message: "multiple errors encountered",
|
||||
errors: nonNils,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type multiError struct {
|
||||
message string
|
||||
errors []error
|
||||
}
|
||||
|
||||
func (e *multiError) Message() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e *multiError) Error() string {
|
||||
result := e.message
|
||||
for _, e := range e.errors {
|
||||
result += fmt.Sprintf("\n %s", e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (e *multiError) Errors() []error {
|
||||
return e.errors
|
||||
}
|
||||
|
||||
type wrappedError struct {
|
||||
message string
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e *wrappedError) Message() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string {
|
||||
return RolledUpErrorMessage(e)
|
||||
}
|
||||
|
||||
func (e *wrappedError) Inner() error {
|
||||
return e.inner
|
||||
}
|
||||
|
||||
func (e *wrappedError) Unwrap() error {
|
||||
return e.inner
|
||||
}
|
||||
Generated
Vendored
+1
-7
@@ -4,16 +4,10 @@
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal // import "go.mongodb.org/mongo-driver/internal"
|
||||
|
||||
// Version is the current version of the driver.
|
||||
var Version = "local build"
|
||||
package handshake
|
||||
|
||||
// LegacyHello is the legacy version of the hello command.
|
||||
var LegacyHello = "isMaster"
|
||||
|
||||
// LegacyHelloLowercase is the lowercase, legacy version of the hello command.
|
||||
var LegacyHelloLowercase = "ismaster"
|
||||
|
||||
// LegacyNotPrimary is the legacy version of the "not primary" server error message.
|
||||
var LegacyNotPrimary = "not master"
|
||||
Generated
Vendored
+5
-5
@@ -4,7 +4,7 @@
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package internal // import "go.mongodb.org/mongo-driver/internal"
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -16,10 +16,10 @@ var DefaultHTTPClient = &http.Client{
|
||||
}
|
||||
|
||||
// CloseIdleHTTPConnections closes any connections which were previously
|
||||
// connected from previous requests but are now sitting idle in
|
||||
// a "keep-alive" state. It does not interrupt any connections currently
|
||||
// in use.
|
||||
// Borrowed from go standard library.
|
||||
// connected from previous requests but are now sitting idle in a "keep-alive"
|
||||
// state. It does not interrupt any connections currently in use.
|
||||
//
|
||||
// Borrowed from the Go standard library.
|
||||
func CloseIdleHTTPConnections(client *http.Client) {
|
||||
type closeIdler interface {
|
||||
CloseIdleConnections()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user