Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26089f0886 | |||
| a1de95a8ce | |||
| f4861bf3c1 | |||
| 7fb3b2f591 | |||
| 0dec68294f | |||
| 69ba548dab | |||
| cfa9ff15bb | |||
| ddf7441152 | |||
| 20b4a3fb84 | |||
| 81f4420a67 |
@@ -13,14 +13,14 @@ require (
|
|||||||
github.com/docker/cli v28.4.0+incompatible
|
github.com/docker/cli v28.4.0+incompatible
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/go-git/go-git/v5 v5.19.1
|
github.com/go-git/go-git/v5 v5.19.2
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/leonelquinteros/gotext v1.7.2
|
github.com/leonelquinteros/gotext v1.7.2
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/moby/term v0.5.2
|
github.com/moby/term v0.5.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/schollz/progressbar/v3 v3.19.1
|
github.com/schollz/progressbar/v3 v3.19.1
|
||||||
golang.org/x/term v0.44.0
|
golang.org/x/term v0.45.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.2
|
gotest.tools/v3 v3.5.2
|
||||||
)
|
)
|
||||||
@@ -124,10 +124,10 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/crypto v0.50.0 // indirect
|
golang.org/x/crypto v0.53.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||||
golang.org/x/net v0.53.0 // indirect
|
golang.org/x/net v0.56.0 // indirect
|
||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.39.0 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||||
@@ -155,7 +155,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
golang.org/x/sys v0.46.0
|
golang.org/x/sys v0.47.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
||||||
|
|||||||
@@ -393,8 +393,8 @@ github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmm
|
|||||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -966,8 +966,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -1043,8 +1043,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -1139,13 +1139,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -1155,8 +1155,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: nginx:1.31.2
|
image: nginx:1.31.3
|
||||||
secrets:
|
secrets:
|
||||||
- test_pass_one
|
- test_pass_one
|
||||||
- test_pass_two
|
- test_pass_two
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: nginx:1.31.2
|
image: nginx:1.31.3
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
+1
@@ -5,3 +5,4 @@ profile.out
|
|||||||
.tmp/
|
.tmp/
|
||||||
.git-dist/
|
.git-dist/
|
||||||
.vscode
|
.vscode
|
||||||
|
build/tools/
|
||||||
|
|||||||
+42
@@ -110,6 +110,48 @@ func (r ReferenceName) IsTag() bool {
|
|||||||
return strings.HasPrefix(string(r), refTagPrefix)
|
return strings.HasPrefix(string(r), refTagPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSafe reports whether the reference name can be safely turned into a path
|
||||||
|
// under the .git directory, mirroring Git's refname_is_safe (refs.c). A name
|
||||||
|
// is safe when it is either:
|
||||||
|
//
|
||||||
|
// - under "refs/", non-empty after the prefix, containing no backslash and
|
||||||
|
// no empty, "." or ".." path component (so it cannot escape the refs/
|
||||||
|
// sub-tree, or alias another name, once turned into a path); or
|
||||||
|
// - a one-level pseudo-ref whose spelling is restricted to [A-Z_]
|
||||||
|
// (e.g. HEAD, ORIG_HEAD, FETCH_HEAD).
|
||||||
|
//
|
||||||
|
// Everything else — a lowercase or mixed one-level name such as "config" or
|
||||||
|
// "index", an absolute or drive-prefixed name, or a refs/ name that escapes —
|
||||||
|
// is unsafe, because it could resolve onto unrelated repository metadata.
|
||||||
|
func (r ReferenceName) IsSafe() bool {
|
||||||
|
s := string(r)
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest, ok := strings.CutPrefix(s, refPrefix); ok {
|
||||||
|
// '\' is a path separator on Windows, so a refs/ name containing one
|
||||||
|
// could escape the sub-tree or alias another name once turned into a
|
||||||
|
// path; reject it outright (check_refname_format forbids '\' too).
|
||||||
|
if rest == "" || strings.Contains(rest, "\\") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for part := range strings.SplitSeq(rest, "/") {
|
||||||
|
if part == "" || part == "." || part == ".." {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if (s[i] < 'A' || s[i] > 'Z') && s[i] != '_' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r ReferenceName) String() string {
|
func (r ReferenceName) String() string {
|
||||||
return string(r)
|
return string(r)
|
||||||
}
|
}
|
||||||
|
|||||||
+57
@@ -16,6 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
"github.com/go-git/go-git/v5/plumbing/hash"
|
||||||
"github.com/go-git/go-git/v5/storage"
|
"github.com/go-git/go-git/v5/storage"
|
||||||
@@ -79,8 +80,52 @@ var (
|
|||||||
// resolve outside the modules/ subtree, mirroring canonical Git's
|
// resolve outside the modules/ subtree, mirroring canonical Git's
|
||||||
// "ignoring suspicious submodule name" defence.
|
// "ignoring suspicious submodule name" defence.
|
||||||
ErrModuleNameEscape = errors.New("submodule name escapes modules/ directory")
|
ErrModuleNameEscape = errors.New("submodule name escapes modules/ directory")
|
||||||
|
// ErrReferenceNameEscape is returned when a reference name would
|
||||||
|
// resolve outside its reference sub-tree once turned into a path
|
||||||
|
// under the .git directory (e.g. a name with a ".." component).
|
||||||
|
ErrReferenceNameEscape = errors.New("reference name escapes the reference storage")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// isPathSep reports whether r is a path separator in reference names.
|
||||||
|
// It treats both '/' and '\\' as separators to harden against cross-OS paths.
|
||||||
|
func isPathSep(r rune) bool { return r == '/' || r == '\\' }
|
||||||
|
|
||||||
|
// validReferenceName rejects reference names that cannot be safely turned into
|
||||||
|
// a path under the .git directory. A loose reference is stored verbatim at
|
||||||
|
// ".git/<name>", so a crafted name — for instance one advertised by a malicious
|
||||||
|
// remote — could climb out of its reference sub-tree and read, overwrite, or
|
||||||
|
// delete unrelated metadata such as .git/config.
|
||||||
|
//
|
||||||
|
// The storage-safety gate is plumbing.ReferenceName.IsSafe, mirroring Git's
|
||||||
|
// refname_is_safe: a name must be under refs/ without escaping it, or be a
|
||||||
|
// [A-Z_] pseudo-ref. This alone rejects absolute, drive-prefixed, escaping and
|
||||||
|
// single-level metadata names. On top of it, this adds filesystem-specific
|
||||||
|
// hardening that IsSafe's literal check does not cover: control characters, and
|
||||||
|
// components a case-insensitive/NTFS/HFS+ filesystem would fold back to "." or
|
||||||
|
// ".." (trailing dots/spaces, Alternate Data Streams, ignorable Unicode code
|
||||||
|
// points), delegated to pathutil.IsHFSDot and pathutil.IsNTFSDot with "." as
|
||||||
|
// the needle — as validSubmoduleName does — and run regardless of host OS.
|
||||||
|
func validReferenceName(name plumbing.ReferenceName) error {
|
||||||
|
if !name.IsSafe() {
|
||||||
|
return fmt.Errorf("%w: %q is not under refs/ nor a valid pseudo-ref", ErrReferenceNameEscape, string(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
s := string(name)
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] < 0x20 || s[i] == 0x7f {
|
||||||
|
return fmt.Errorf("%w: %q", ErrReferenceNameEscape, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, part := range strings.FieldsFunc(s, isPathSep) {
|
||||||
|
// IsNTFSDot/IsHFSDot with a "." needle match ".." and its disguises
|
||||||
|
// but not a bare ".", so reject that component explicitly too.
|
||||||
|
if part == "." || pathutil.IsHFSDot(part, ".") || pathutil.IsNTFSDot(part, ".", "") {
|
||||||
|
return fmt.Errorf("%w: %q", ErrReferenceNameEscape, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Options holds configuration for the storage.
|
// Options holds configuration for the storage.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// ExclusiveAccess means that the filesystem is not modified externally
|
// ExclusiveAccess means that the filesystem is not modified externally
|
||||||
@@ -706,6 +751,10 @@ func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
|
func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
|
||||||
|
if err := validReferenceName(r.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
switch r.Type() {
|
switch r.Type() {
|
||||||
case plumbing.SymbolicReference:
|
case plumbing.SymbolicReference:
|
||||||
@@ -741,6 +790,10 @@ func (d *DotGit) Refs() ([]*plumbing.Reference, error) {
|
|||||||
|
|
||||||
// Ref returns the reference for a given reference name.
|
// Ref returns the reference for a given reference name.
|
||||||
func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) {
|
func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) {
|
||||||
|
if err := validReferenceName(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
ref, err := d.readReferenceFile(".", name.String())
|
ref, err := d.readReferenceFile(".", name.String())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ref, nil
|
return ref, nil
|
||||||
@@ -804,6 +857,10 @@ func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, er
|
|||||||
|
|
||||||
// RemoveRef removes a reference by name.
|
// RemoveRef removes a reference by name.
|
||||||
func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error {
|
func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error {
|
||||||
|
if err := validReferenceName(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
path := d.fs.Join(".", name.String())
|
path := d.fs.Join(".", name.String())
|
||||||
_, err := d.fs.Stat(path)
|
_, err := d.fs.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
+67
@@ -583,6 +583,10 @@ func (w *Worktree) checkoutChangeSubmodule(name string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := w.clearBlockingSymlinks(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := w.Filesystem.MkdirAll(name, mode); err != nil {
|
if err := w.Filesystem.MkdirAll(name, mode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -626,7 +630,70 @@ func (w *Worktree) checkoutChangeRegularFile(name string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearBlockingSymlinks removes a symlink that is in the way of
|
||||||
|
// materialising name, so the checkout writes a real entry in its place
|
||||||
|
// instead of following the link out of the worktree. Two cases:
|
||||||
|
//
|
||||||
|
// - a leading directory component that is a symlink (e.g. "s" while
|
||||||
|
// writing "s/config", where "s" links to ".git"): OpenFile/MkdirAll
|
||||||
|
// would traverse it, so the write would land under the link's target.
|
||||||
|
// - the final component itself being a symlink (e.g. writing "s" while
|
||||||
|
// "s" links to ".git/config"): OpenFile with O_TRUNC, or Symlink,
|
||||||
|
// would follow/replace through it and clobber the target.
|
||||||
|
//
|
||||||
|
// A symlink can never be a legitimate parent of, or the destination for,
|
||||||
|
// a tracked entry, so removing it is always correct. This mirrors upstream
|
||||||
|
// Git's forced checkout, which unlinks a blocking symlink in the leading
|
||||||
|
// path (create_directories) and unlinks an existing entry before
|
||||||
|
// write_entry.
|
||||||
|
// https://github.com/git/git/blob/v2.54.0/entry.c#L50
|
||||||
|
func (w *Worktree) clearBlockingSymlinks(name string) error {
|
||||||
|
var dirs []string
|
||||||
|
for dir := filepath.Dir(name); dir != "." && dir != "" && dir != string(filepath.Separator); dir = filepath.Dir(dir) {
|
||||||
|
dirs = append(dirs, dir)
|
||||||
|
}
|
||||||
|
// Leading components, shallowest-first: removing the shallowest symlink
|
||||||
|
// invalidates every component beneath it, so a single removal is enough.
|
||||||
|
for i := len(dirs) - 1; i >= 0; i-- {
|
||||||
|
fi, err := w.Filesystem.Lstat(dirs[i])
|
||||||
|
if err != nil {
|
||||||
|
// A missing component is created as a real directory by the
|
||||||
|
// checkout. Any other error means we cannot tell whether it is
|
||||||
|
// a symlink, so surface it instead of leaving a blocking link in
|
||||||
|
// place and failing later in a harder-to-diagnose way.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return w.Filesystem.Remove(dirs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Final component: an existing symlink here would be followed by the
|
||||||
|
// subsequent OpenFile/Symlink/MkdirAll, so replace it.
|
||||||
|
fi, err := w.Filesystem.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return w.Filesystem.Remove(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *Worktree) checkoutFile(f *object.File) (err error) {
|
func (w *Worktree) checkoutFile(f *object.File) (err error) {
|
||||||
|
// checkoutFile is the materialisation boundary for tracked entries.
|
||||||
|
// Remove any blocking symlink first so the subsequent OpenFile or
|
||||||
|
// Symlink call writes the entry itself instead of following a planted
|
||||||
|
// final-component link in the underlying filesystem.
|
||||||
|
if err := w.clearBlockingSymlinks(f.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
mode, err := f.Mode.ToOSFileMode()
|
mode, err := f.Mode.ToOSFileMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+100
-15
@@ -35,10 +35,25 @@ func defaultProtectNTFS() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// worktreeFilesystem wraps a billy.Filesystem and validates every path passed
|
// worktreeFilesystem wraps a billy.Filesystem and validates every path it
|
||||||
// to a mutating operation. This prevents writing to, or deleting from,
|
// is handed, so worktree operations cannot use dangerous paths at the
|
||||||
// dangerous locations (e.g. .git/*, ../) regardless of which worktree
|
// boundary. Two layers apply:
|
||||||
// code path triggers the operation.
|
//
|
||||||
|
// - validPath rejects dangerous path *strings*: .git and its HFS+/NTFS
|
||||||
|
// variants, "..", control characters, volume names.
|
||||||
|
// - validNoLeadingSymlink rejects paths whose leading directories
|
||||||
|
// already exist on disk as symlinks, so a write or delete cannot
|
||||||
|
// follow a planted link out of the tree.
|
||||||
|
//
|
||||||
|
// Both layers run on every mutating operation (validWritePath) and every
|
||||||
|
// read (validReadPath). Chroot additionally refuses a symlink as the final
|
||||||
|
// component, so a sub-filesystem such as a submodule worktree cannot be
|
||||||
|
// scoped to a redirected target.
|
||||||
|
//
|
||||||
|
// The wrapper intentionally stops at leading-component traversal. Callers
|
||||||
|
// that need final-component no-follow semantics for materialisation
|
||||||
|
// (checkoutFile) enforce that directly by removing the blocking symlink
|
||||||
|
// before opening the destination path.
|
||||||
type worktreeFilesystem struct {
|
type worktreeFilesystem struct {
|
||||||
billy.Filesystem
|
billy.Filesystem
|
||||||
protectNTFS bool
|
protectNTFS bool
|
||||||
@@ -50,7 +65,7 @@ func newWorktreeFilesystem(fs billy.Filesystem, protectNTFS, protectHFS bool) *w
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sfs *worktreeFilesystem) Create(filename string) (billy.File, error) {
|
func (sfs *worktreeFilesystem) Create(filename string) (billy.File, error) {
|
||||||
if err := sfs.validPath(filename); err != nil {
|
if err := sfs.validWritePath(filename); err != nil {
|
||||||
return nil, fmt.Errorf("create: %w", err)
|
return nil, fmt.Errorf("create: %w", err)
|
||||||
}
|
}
|
||||||
return sfs.Filesystem.Create(filename)
|
return sfs.Filesystem.Create(filename)
|
||||||
@@ -64,7 +79,7 @@ func (sfs *worktreeFilesystem) Open(filename string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sfs *worktreeFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
func (sfs *worktreeFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
||||||
if err := sfs.validPath(filename); err != nil {
|
if err := sfs.validWritePath(filename); err != nil {
|
||||||
return nil, fmt.Errorf("openfile: %w", err)
|
return nil, fmt.Errorf("openfile: %w", err)
|
||||||
}
|
}
|
||||||
return sfs.Filesystem.OpenFile(filename, flag, perm)
|
return sfs.Filesystem.OpenFile(filename, flag, perm)
|
||||||
@@ -78,14 +93,14 @@ func (sfs *worktreeFilesystem) Stat(filename string) (os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sfs *worktreeFilesystem) Remove(filename string) error {
|
func (sfs *worktreeFilesystem) Remove(filename string) error {
|
||||||
if err := sfs.validPath(filename); err != nil {
|
if err := sfs.validWritePath(filename); err != nil {
|
||||||
return fmt.Errorf("remove: %w", err)
|
return fmt.Errorf("remove: %w", err)
|
||||||
}
|
}
|
||||||
return sfs.Filesystem.Remove(filename)
|
return sfs.Filesystem.Remove(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sfs *worktreeFilesystem) Rename(from, to string) error {
|
func (sfs *worktreeFilesystem) Rename(from, to string) error {
|
||||||
if err := sfs.validPath(from, to); err != nil {
|
if err := sfs.validWritePath(from, to); err != nil {
|
||||||
return fmt.Errorf("rename: %w", err)
|
return fmt.Errorf("rename: %w", err)
|
||||||
}
|
}
|
||||||
return sfs.Filesystem.Rename(from, to)
|
return sfs.Filesystem.Rename(from, to)
|
||||||
@@ -106,7 +121,7 @@ func (sfs *worktreeFilesystem) Lstat(filename string) (os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sfs *worktreeFilesystem) Symlink(target, link string) error {
|
func (sfs *worktreeFilesystem) Symlink(target, link string) error {
|
||||||
if err := sfs.validPath(link); err != nil {
|
if err := sfs.validWritePath(link); err != nil {
|
||||||
return fmt.Errorf("symlink: %w", err)
|
return fmt.Errorf("symlink: %w", err)
|
||||||
}
|
}
|
||||||
if err := sfs.validSymlinkName(link); err != nil {
|
if err := sfs.validSymlinkName(link); err != nil {
|
||||||
@@ -131,7 +146,7 @@ func (sfs *worktreeFilesystem) MkdirAll(path string, perm os.FileMode) error {
|
|||||||
if path == "" || path == "." || path == "/" {
|
if path == "" || path == "." || path == "/" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := sfs.validPath(path); err != nil {
|
if err := sfs.validWritePath(path); err != nil {
|
||||||
return fmt.Errorf("mkdirall: %w", err)
|
return fmt.Errorf("mkdirall: %w", err)
|
||||||
}
|
}
|
||||||
return sfs.Filesystem.MkdirAll(path, perm)
|
return sfs.Filesystem.MkdirAll(path, perm)
|
||||||
@@ -145,18 +160,39 @@ func (sfs *worktreeFilesystem) Chroot(path string) (billy.Filesystem, error) {
|
|||||||
if err := sfs.validReadPath(path); err != nil {
|
if err := sfs.validReadPath(path); err != nil {
|
||||||
return nil, fmt.Errorf("chroot: %w", err)
|
return nil, fmt.Errorf("chroot: %w", err)
|
||||||
}
|
}
|
||||||
|
// Chroot scopes a sub-filesystem to path, so the final component must
|
||||||
|
// be a real directory too: a symlink there would silently redirect the
|
||||||
|
// scope (e.g. a submodule worktree) to a target outside the tree. This
|
||||||
|
// is the "valid path, wrong target" case that validNoLeadingSymlink,
|
||||||
|
// which only inspects leading components, does not cover.
|
||||||
|
//
|
||||||
|
// A non-existent target is fine: Chroot creates it as a real
|
||||||
|
// directory. Any other Lstat error means we cannot prove the target
|
||||||
|
// is not a symlink, so fail closed rather than scope through it.
|
||||||
|
if fi, err := sfs.Filesystem.Lstat(path); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("chroot: cannot stat %q: %w", path, err)
|
||||||
|
}
|
||||||
|
} else if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return nil, fmt.Errorf("chroot: invalid path %q: is a symlink", path)
|
||||||
|
}
|
||||||
return sfs.Filesystem.Chroot(path)
|
return sfs.Filesystem.Chroot(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validReadPath is like validPath but treats the empty string and "." as
|
// validReadPath is like validWritePath but treats the empty string and "."
|
||||||
// valid references to the worktree root. Read-side operations on the root
|
// as valid references to the worktree root. Read-side operations on the
|
||||||
// (e.g. ReadDir(""), Lstat(".")) are legitimate; mutating the root itself
|
// root (e.g. ReadDir(""), Lstat(".")) are legitimate. Mutating the root
|
||||||
// is not, so write-side operations continue to use validPath directly.
|
// itself is not, so write-side operations reject it via validPath. Reads
|
||||||
|
// are still refused through a leading symlink, so the wrapper never
|
||||||
|
// follows a planted link even on the read surface.
|
||||||
func (sfs *worktreeFilesystem) validReadPath(p string) error {
|
func (sfs *worktreeFilesystem) validReadPath(p string) error {
|
||||||
if p == "" || p == "." || p == "/" {
|
if p == "" || p == "." || p == "/" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return sfs.validPath(p)
|
if err := sfs.validPath(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sfs.validNoLeadingSymlink(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errUnsupportedOperation = errors.New("unsupported operation")
|
var errUnsupportedOperation = errors.New("unsupported operation")
|
||||||
@@ -232,6 +268,55 @@ func (sfs *worktreeFilesystem) validPath(paths ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validWritePath validates paths for mutating operations. It layers the
|
||||||
|
// filesystem-state check validNoLeadingSymlink on top of the string-only
|
||||||
|
// checks in validPath, so a write can neither name a dangerous path nor
|
||||||
|
// reach one by traversing an existing symlink. Every mutating method on
|
||||||
|
// the wrapper funnels through here, so the leading-symlink invariant holds
|
||||||
|
// for all worktree writers without each call site having to remember it.
|
||||||
|
func (sfs *worktreeFilesystem) validWritePath(paths ...string) error {
|
||||||
|
if err := sfs.validPath(paths...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sfs.validNoLeadingSymlink(paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validNoLeadingSymlink rejects paths whose leading directory components
|
||||||
|
// resolve through a symlink that already exists on the underlying
|
||||||
|
// filesystem. validPath guards the path string. This guards the on-disk
|
||||||
|
// state, so a write or delete cannot reach outside the worktree by
|
||||||
|
// traversing a symlink that a tree or an earlier step left in place.
|
||||||
|
//
|
||||||
|
// This is the fail-closed backstop for the whole class. Callers that want
|
||||||
|
// upstream's replace-and-continue behaviour (checkout) remove the blocking
|
||||||
|
// symlink first via clearBlockingSymlinks, so no symlink remains when the
|
||||||
|
// write reaches the wrapper. Callers that do not get a safe error,
|
||||||
|
// matching upstream Git refusing rather than following the link. See
|
||||||
|
// has_symlink_leading_path (symlinks.c) and the check_leading_path guard
|
||||||
|
// in unlink_entry (entry.c).
|
||||||
|
func (sfs *worktreeFilesystem) validNoLeadingSymlink(paths ...string) error {
|
||||||
|
for _, p := range paths {
|
||||||
|
for dir := filepath.Dir(p); dir != "." && dir != "" && dir != string(filepath.Separator); dir = filepath.Dir(dir) {
|
||||||
|
fi, err := sfs.Filesystem.Lstat(dir)
|
||||||
|
if err != nil {
|
||||||
|
// A missing ancestor is materialised as a real directory,
|
||||||
|
// so it cannot be a symlink and is safe to skip. Any other
|
||||||
|
// error (permission, I/O) means we cannot prove the
|
||||||
|
// component is not a symlink, so fail closed rather than
|
||||||
|
// let the operation traverse an unverified component.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid path %q: cannot stat leading component %q: %w", p, dir, err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return fmt.Errorf("invalid path %q: leading component %q is a symlink", p, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validSymlinkName checks the per-component name of a symlink for
|
// validSymlinkName checks the per-component name of a symlink for
|
||||||
// dotfile names that attackers can use to trick a checkout into
|
// dotfile names that attackers can use to trick a checkout into
|
||||||
// writing a dangerous symlink. Each path component is compared
|
// writing a dangerous symlink. Each path component is compared
|
||||||
|
|||||||
+1
-1
@@ -371,7 +371,7 @@ func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipSta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path = filepath.Clean(path)
|
path = filepath.ToSlash(filepath.Clean(path))
|
||||||
|
|
||||||
if err != nil || !fi.IsDir() {
|
if err != nil || !fi.IsDir() {
|
||||||
added, h, err = w.doAddFile(idx, s, path, ignorePattern)
|
added, h, err = w.doAddFile(idx, s, path, ignorePattern)
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26@sha256:f96cc555eb8db430159a3aa6797cd5bae561945b7b0fe7d0e284c63a3b291609
|
FROM golang:1.26@sha256:3aff6657219a4d9c14e27fb1d8976c49c29fddb70ba835014f477e1c70636647
|
||||||
|
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
ENV GOARCH=arm
|
ENV GOARCH=arm
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26@sha256:f96cc555eb8db430159a3aa6797cd5bae561945b7b0fe7d0e284c63a3b291609
|
FROM golang:1.26@sha256:3aff6657219a4d9c14e27fb1d8976c49c29fddb70ba835014f477e1c70636647
|
||||||
|
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
ENV GOARCH=arm64
|
ENV GOARCH=arm64
|
||||||
|
|||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
// Copyright 2025 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build go1.25
|
|
||||||
|
|
||||||
package blake2b
|
|
||||||
|
|
||||||
import "hash"
|
|
||||||
|
|
||||||
var _ hash.XOF = (*xof)(nil)
|
|
||||||
+13
-8
@@ -11,6 +11,7 @@
|
|||||||
package hkdf
|
package hkdf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hkdf"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
@@ -24,15 +25,19 @@ import (
|
|||||||
// Expand invocations and different context values. Most common scenarios,
|
// Expand invocations and different context values. Most common scenarios,
|
||||||
// including the generation of multiple keys, should use New instead.
|
// including the generation of multiple keys, should use New instead.
|
||||||
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
||||||
if salt == nil {
|
// Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
|
||||||
salt = make([]byte, hash().Size())
|
// key (which in HKDF is the salt). The only possible error is FIPS 140
|
||||||
|
// enforcement of the hash, which had to panic under this API anyway. We
|
||||||
|
// don't use the stdlib Expand, because it switched to returning a []byte
|
||||||
|
// instead of an io.Reader, and Expand uses the HMAC key as a key.
|
||||||
|
out, err := hkdf.Extract(hash, secret, salt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
extractor := hmac.New(hash, salt)
|
return out
|
||||||
extractor.Write(secret)
|
|
||||||
return extractor.Sum(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type hkdf struct {
|
type hkdfReader struct {
|
||||||
expander hash.Hash
|
expander hash.Hash
|
||||||
size int
|
size int
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ type hkdf struct {
|
|||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *hkdf) Read(p []byte) (int, error) {
|
func (f *hkdfReader) Read(p []byte) (int, error) {
|
||||||
// Check whether enough data can be generated
|
// Check whether enough data can be generated
|
||||||
need := len(p)
|
need := len(p)
|
||||||
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
remains := len(f.buf) + int(255-f.counter+1)*f.size
|
||||||
@@ -84,7 +89,7 @@ func (f *hkdf) Read(p []byte) (int, error) {
|
|||||||
// 3.3. Most common scenarios will want to use New instead.
|
// 3.3. Most common scenarios will want to use New instead.
|
||||||
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
||||||
expander := hmac.New(hash, pseudorandomKey)
|
expander := hmac.New(hash, pseudorandomKey)
|
||||||
return &hkdf{expander, expander.Size(), info, 1, nil, nil}
|
return &hkdfReader{expander, expander.Size(), info, 1, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a Reader, from which keys can be read, using the given hash,
|
// New returns a Reader, from which keys can be read, using the given hash,
|
||||||
|
|||||||
+233
-3
@@ -26,6 +26,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
@@ -307,17 +308,50 @@ func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
|||||||
}, record.Rest, nil
|
}, record.Rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pipelineMaxInFlight is the maximum number of outstanding requests the
|
||||||
|
// client will pipeline to the agent before applying backpressure.
|
||||||
|
const pipelineMaxInFlight = 32
|
||||||
|
|
||||||
// client is a client for an ssh-agent process.
|
// client is a client for an ssh-agent process.
|
||||||
|
//
|
||||||
|
// Exactly one of pipeline / (mu, conn) is set, chosen by NewClient
|
||||||
|
// based on whether the underlying transport implements io.Closer.
|
||||||
type client struct {
|
type client struct {
|
||||||
// conn is typically a *net.UnixConn
|
// pipeline, if non-nil, dispatches requests over a pipelined
|
||||||
|
// connection: requests are written as soon as the wire is
|
||||||
|
// available and responses are routed back to per-call reply
|
||||||
|
// channels in FIFO order by a background reader goroutine.
|
||||||
|
pipeline *pipeline
|
||||||
|
|
||||||
|
// mu and conn are used in fully-serialized mode, when the
|
||||||
|
// transport does not implement io.Closer. Each call takes mu,
|
||||||
|
// writes its request, reads the matching response, and releases
|
||||||
|
// mu before returning. There is no background goroutine.
|
||||||
|
mu sync.Mutex
|
||||||
conn io.ReadWriter
|
conn io.ReadWriter
|
||||||
// mu is used to prevent concurrent access to the agent
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||||
// the given connection.
|
// the given connection.
|
||||||
|
//
|
||||||
|
// If rw also implements io.Closer (like *net.UnixConn and ssh.Channel
|
||||||
|
// do), the returned client pipelines concurrent requests over the
|
||||||
|
// connection: callers can issue Sign and other operations from
|
||||||
|
// multiple goroutines and they will be written to the agent as soon
|
||||||
|
// as the wire is available, rather than waiting for the previous
|
||||||
|
// responses. The ssh-agent protocol still requires responses to be
|
||||||
|
// returned in request order, so a slow request delays subsequent
|
||||||
|
// responses on the same connection (head-of-line blocking).
|
||||||
|
//
|
||||||
|
// Pipelining requires io.Closer because, on a Write error, the
|
||||||
|
// background reader goroutine must be unblocked by closing the
|
||||||
|
// underlying connection. When rw does not implement io.Closer
|
||||||
|
// this is not possible, so NewClient falls back to fully
|
||||||
|
// serializing each request: a single in-flight call at a time.
|
||||||
func NewClient(rw io.ReadWriter) ExtendedAgent {
|
func NewClient(rw io.ReadWriter) ExtendedAgent {
|
||||||
|
if rwc, ok := rw.(io.ReadWriteCloser); ok {
|
||||||
|
return &client{pipeline: newPipeline(rwc)}
|
||||||
|
}
|
||||||
return &client{conn: rw}
|
return &client{conn: rw}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +374,16 @@ func (c *client) call(req []byte) (reply interface{}, err error) {
|
|||||||
// bytes of the response are returned; no unmarshalling is
|
// bytes of the response are returned; no unmarshalling is
|
||||||
// performed on the response.
|
// performed on the response.
|
||||||
func (c *client) callRaw(req []byte) (reply []byte, err error) {
|
func (c *client) callRaw(req []byte) (reply []byte, err error) {
|
||||||
|
if c.pipeline != nil {
|
||||||
|
return c.pipeline.call(req)
|
||||||
|
}
|
||||||
|
return c.serialCall(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialCall implements the fully-serialized request/response path
|
||||||
|
// used when the transport is not an io.Closer. It writes req under mu
|
||||||
|
// and reads the matching response before returning.
|
||||||
|
func (c *client) serialCall(req []byte) (reply []byte, err error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -577,6 +621,9 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
|||||||
Constraints: constraints,
|
Constraints: constraints,
|
||||||
})
|
})
|
||||||
case ed25519.PrivateKey:
|
case ed25519.PrivateKey:
|
||||||
|
if len(k) != ed25519.PrivateKeySize {
|
||||||
|
return fmt.Errorf("agent: bad ED25519 key size: %d", len(k))
|
||||||
|
}
|
||||||
req = ssh.Marshal(ed25519KeyMsg{
|
req = ssh.Marshal(ed25519KeyMsg{
|
||||||
Type: ssh.KeyAlgoED25519,
|
Type: ssh.KeyAlgoED25519,
|
||||||
Pub: []byte(k)[32:],
|
Pub: []byte(k)[32:],
|
||||||
@@ -588,6 +635,9 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
|||||||
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
||||||
// We still support the pointer variant for backwards compatibility.
|
// We still support the pointer variant for backwards compatibility.
|
||||||
case *ed25519.PrivateKey:
|
case *ed25519.PrivateKey:
|
||||||
|
if len(*k) != ed25519.PrivateKeySize {
|
||||||
|
return fmt.Errorf("agent: bad ED25519 key size: %d", len(*k))
|
||||||
|
}
|
||||||
req = ssh.Marshal(ed25519KeyMsg{
|
req = ssh.Marshal(ed25519KeyMsg{
|
||||||
Type: ssh.KeyAlgoED25519,
|
Type: ssh.KeyAlgoED25519,
|
||||||
Pub: []byte(*k)[32:],
|
Pub: []byte(*k)[32:],
|
||||||
@@ -663,6 +713,13 @@ func (c *client) Add(key AddedKey) error {
|
|||||||
constraints = append(constraints, agentConstrainConfirm)
|
constraints = append(constraints, agentConstrainConfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ext := range key.ConstraintExtensions {
|
||||||
|
constraints = append(constraints, ssh.Marshal(constrainExtensionAgentMsg{
|
||||||
|
ExtensionName: ext.ExtensionName,
|
||||||
|
ExtensionDetails: ext.ExtensionDetails,
|
||||||
|
})...)
|
||||||
|
}
|
||||||
|
|
||||||
cert := key.Certificate
|
cert := key.Certificate
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||||
@@ -705,6 +762,9 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
|||||||
Constraints: constraints,
|
Constraints: constraints,
|
||||||
})
|
})
|
||||||
case ed25519.PrivateKey:
|
case ed25519.PrivateKey:
|
||||||
|
if len(k) != ed25519.PrivateKeySize {
|
||||||
|
return fmt.Errorf("agent: bad ED25519 key size: %d", len(k))
|
||||||
|
}
|
||||||
req = ssh.Marshal(ed25519CertMsg{
|
req = ssh.Marshal(ed25519CertMsg{
|
||||||
Type: cert.Type(),
|
Type: cert.Type(),
|
||||||
CertBytes: cert.Marshal(),
|
CertBytes: cert.Marshal(),
|
||||||
@@ -717,6 +777,9 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
|||||||
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
// general idiom is to pass ed25519.PrivateKey by value, not by pointer.
|
||||||
// We still support the pointer variant for backwards compatibility.
|
// We still support the pointer variant for backwards compatibility.
|
||||||
case *ed25519.PrivateKey:
|
case *ed25519.PrivateKey:
|
||||||
|
if len(*k) != ed25519.PrivateKeySize {
|
||||||
|
return fmt.Errorf("agent: bad ED25519 key size: %d", len(*k))
|
||||||
|
}
|
||||||
req = ssh.Marshal(ed25519CertMsg{
|
req = ssh.Marshal(ed25519CertMsg{
|
||||||
Type: cert.Type(),
|
Type: cert.Type(),
|
||||||
CertBytes: cert.Marshal(),
|
CertBytes: cert.Marshal(),
|
||||||
@@ -854,3 +917,170 @@ func (c *client) Extension(extensionType string, contents []byte) ([]byte, error
|
|||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pipelineResult carries either a raw agent reply or an error back to a
|
||||||
|
// caller waiting on the response channel.
|
||||||
|
type pipelineResult struct {
|
||||||
|
reply []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipeline implements request pipelining over a single agent connection.
|
||||||
|
//
|
||||||
|
// Writers serialize on writeMu to both register a reply channel in the
|
||||||
|
// pending FIFO queue and write the request bytes on the wire; the two
|
||||||
|
// must be atomic so the queue order matches the wire order. A single
|
||||||
|
// reader goroutine decodes responses from the connection and dispatches
|
||||||
|
// each one to the channel at the head of the queue.
|
||||||
|
//
|
||||||
|
// pending is a chan-of-chan acting as a FIFO queue with a fixed
|
||||||
|
// capacity of pipelineMaxInFlight. The outer channel provides ordering
|
||||||
|
// (reads happen in send order) and natural backpressure (a full queue
|
||||||
|
// blocks new writers). Each inner channel is buffered with capacity
|
||||||
|
// one and is sent to exactly once: either by the reader goroutine
|
||||||
|
// with the agent reply, or by shutdown with the terminal error during
|
||||||
|
// drain. The cap-one buffer makes the producer's send non-blocking,
|
||||||
|
// so the reader and shutdown never have to wait for the caller to be
|
||||||
|
// scheduled on the receive.
|
||||||
|
//
|
||||||
|
// When the reader goroutine exits (on read error or protocol
|
||||||
|
// violation), it closes exitCh to wake any writer blocked on the
|
||||||
|
// pending queue, then serializes with any in-flight writer to close
|
||||||
|
// the pending channel, and finally drains the remaining entries
|
||||||
|
// delivering the terminal error to each waiting caller. The
|
||||||
|
// pipeline relies on conn implementing io.Closer so a writer that
|
||||||
|
// hits a Write error can close the connection to unblock the reader
|
||||||
|
// goroutine; NewClient is responsible for only constructing a
|
||||||
|
// pipeline when this guarantee holds.
|
||||||
|
type pipeline struct {
|
||||||
|
conn io.ReadWriteCloser
|
||||||
|
|
||||||
|
writeMu sync.Mutex
|
||||||
|
// pending is the FIFO queue of reply channels with capacity
|
||||||
|
// pipelineMaxInFlight. See type-level documentation.
|
||||||
|
pending chan chan pipelineResult
|
||||||
|
exitCh chan struct{}
|
||||||
|
|
||||||
|
// err carries the terminal error to callers blocked on a closed
|
||||||
|
// pipeline. It is stored exactly once by the reader goroutine
|
||||||
|
// before exitCh is closed; every read happens after observing
|
||||||
|
// exitCh closed, so the load synchronises through the close and
|
||||||
|
// is guaranteed to return the stored value (never nil).
|
||||||
|
err atomic.Pointer[error]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPipeline(conn io.ReadWriteCloser) *pipeline {
|
||||||
|
p := &pipeline{
|
||||||
|
conn: conn,
|
||||||
|
pending: make(chan chan pipelineResult, pipelineMaxInFlight),
|
||||||
|
exitCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go p.readLoop()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLoop decodes responses from conn and dispatches them in FIFO order
|
||||||
|
// to reply channels in pending. On any failure it invokes shutdown.
|
||||||
|
func (p *pipeline) readLoop() {
|
||||||
|
var finalErr error
|
||||||
|
for {
|
||||||
|
var sizeBuf [4]byte
|
||||||
|
if _, err := io.ReadFull(p.conn, sizeBuf[:]); err != nil {
|
||||||
|
finalErr = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
respSize := binary.BigEndian.Uint32(sizeBuf[:])
|
||||||
|
if respSize > maxAgentResponseBytes {
|
||||||
|
finalErr = errors.New("response too large")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf := make([]byte, respSize)
|
||||||
|
if _, err := io.ReadFull(p.conn, buf); err != nil {
|
||||||
|
finalErr = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Successful writes always enqueue before sending bytes, so
|
||||||
|
// pending has a waiting channel for this response.
|
||||||
|
ch := <-p.pending
|
||||||
|
// The reply channel is buffered with capacity 1 and is only
|
||||||
|
// ever written to once, so this send cannot block.
|
||||||
|
ch <- pipelineResult{reply: buf}
|
||||||
|
}
|
||||||
|
p.shutdown(clientErr(finalErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown is called exactly once, from readLoop, when the reader is
|
||||||
|
// terminating. It unblocks pending writers and fails all in-flight
|
||||||
|
// requests with finalErr.
|
||||||
|
func (p *pipeline) shutdown(finalErr error) {
|
||||||
|
// Publish the terminal error before closing exitCh so any
|
||||||
|
// writer that subsequently observes exitCh closed sees err.
|
||||||
|
p.err.Store(&finalErr)
|
||||||
|
|
||||||
|
// Wake any writer blocked waiting for a slot in the pending queue.
|
||||||
|
close(p.exitCh)
|
||||||
|
|
||||||
|
// Wait for any writer currently inside its critical section to
|
||||||
|
// complete. After this lock, no new writer can reach the send on
|
||||||
|
// pending: they will observe exitCh closed in the select and bail
|
||||||
|
// out before attempting the send.
|
||||||
|
p.writeMu.Lock()
|
||||||
|
close(p.pending)
|
||||||
|
p.writeMu.Unlock()
|
||||||
|
|
||||||
|
// Drain entries that were enqueued but never answered, delivering
|
||||||
|
// the terminal error to their waiting callers. The reply channels
|
||||||
|
// are buffered (cap 1) and written to exactly once, so these sends
|
||||||
|
// cannot block.
|
||||||
|
for ch := range p.pending {
|
||||||
|
ch <- pipelineResult{err: finalErr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call sends req to the agent and returns the matching raw response.
|
||||||
|
func (p *pipeline) call(req []byte) ([]byte, error) {
|
||||||
|
replyCh := make(chan pipelineResult, 1)
|
||||||
|
|
||||||
|
p.writeMu.Lock()
|
||||||
|
|
||||||
|
// Priority check: if the reader has already finished shutdown,
|
||||||
|
// pending is closed and sending to it would panic. Bail out now.
|
||||||
|
// Once we pass this check while holding writeMu, shutdown cannot
|
||||||
|
// complete close(pending) until we release writeMu, so the send
|
||||||
|
// below is safe against concurrent closure.
|
||||||
|
select {
|
||||||
|
case <-p.exitCh:
|
||||||
|
p.writeMu.Unlock()
|
||||||
|
return nil, *p.err.Load()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue the reply channel before writing the request, so FIFO
|
||||||
|
// order on the wire matches FIFO order in the pending queue. The
|
||||||
|
// exitCh arm handles the case where the reader errors while we
|
||||||
|
// block on a full queue.
|
||||||
|
select {
|
||||||
|
case p.pending <- replyCh:
|
||||||
|
case <-p.exitCh:
|
||||||
|
p.writeMu.Unlock()
|
||||||
|
return nil, *p.err.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := make([]byte, 4+len(req))
|
||||||
|
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||||
|
copy(msg[4:], req)
|
||||||
|
_, werr := p.conn.Write(msg)
|
||||||
|
p.writeMu.Unlock()
|
||||||
|
|
||||||
|
if werr != nil {
|
||||||
|
// The connection is in an undefined state. Close it so the
|
||||||
|
// reader unblocks promptly and triggers shutdown for every
|
||||||
|
// other in-flight caller. NewClient guarantees conn is a
|
||||||
|
// real io.Closer when the pipeline is in use.
|
||||||
|
p.conn.Close()
|
||||||
|
return nil, clientErr(werr)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := <-replyCh
|
||||||
|
return res.reply, res.err
|
||||||
|
}
|
||||||
|
|||||||
+18
-5
@@ -32,8 +32,10 @@ type keyring struct {
|
|||||||
|
|
||||||
var errLocked = errors.New("agent: locked")
|
var errLocked = errors.New("agent: locked")
|
||||||
|
|
||||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
// NewKeyring returns an Agent that holds keys in memory. It is safe for
|
||||||
// for concurrent use by multiple goroutines.
|
// concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// The returned Agent only supports the "lifetime" constraint.
|
||||||
func NewKeyring() Agent {
|
func NewKeyring() Agent {
|
||||||
return &keyring{}
|
return &keyring{}
|
||||||
}
|
}
|
||||||
@@ -143,15 +145,26 @@ func (r *keyring) List() ([]*Key, error) {
|
|||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert adds a private key to the keyring. If a certificate
|
// Add adds a private key to the keyring. If a certificate is given, that
|
||||||
// is given, that certificate is added as public key. Note that
|
// certificate is added as public key.
|
||||||
// any constraints given are ignored.
|
//
|
||||||
|
// Add returns an error if key contains ConstraintExtensions or
|
||||||
|
// ConfirmBeforeUse.
|
||||||
func (r *keyring) Add(key AddedKey) error {
|
func (r *keyring) Add(key AddedKey) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
if r.locked {
|
if r.locked {
|
||||||
return errLocked
|
return errLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.ConfirmBeforeUse {
|
||||||
|
return errors.New("agent: confirm before use constraint is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(key.ConstraintExtensions) > 0 {
|
||||||
|
return errors.New("agent: constraint extensions are present but not supported")
|
||||||
|
}
|
||||||
|
|
||||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+32
-4
@@ -240,13 +240,35 @@ func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkRSAKeyParams enforces the same bounds as parseRSA in the ssh
|
||||||
|
// package, and additionally caps the prime factors. Without this,
|
||||||
|
// the rsa.PrivateKey built from an Add request would call Precompute()
|
||||||
|
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
|
||||||
|
// |p| and can consume excessive CPU on oversized keys.
|
||||||
|
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
|
||||||
|
if N.BitLen() > 8192 {
|
||||||
|
return errors.New("agent: RSA modulus too large")
|
||||||
|
}
|
||||||
|
if P.BitLen() > 4096 || Q.BitLen() > 4096 {
|
||||||
|
return errors.New("agent: RSA prime too large")
|
||||||
|
}
|
||||||
|
if E.BitLen() > 24 {
|
||||||
|
return errors.New("agent: RSA public exponent too large")
|
||||||
|
}
|
||||||
|
e := E.Int64()
|
||||||
|
if e < 3 || e&1 == 0 {
|
||||||
|
return errors.New("agent: incorrect RSA public exponent")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseRSAKey(req []byte) (*AddedKey, error) {
|
func parseRSAKey(req []byte) (*AddedKey, error) {
|
||||||
var k rsaKeyMsg
|
var k rsaKeyMsg
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if k.E.BitLen() > 30 {
|
if err := checkRSAKeyParams(k.N, k.E, k.P, k.Q); err != nil {
|
||||||
return nil, errors.New("agent: RSA public exponent too large")
|
return nil, err
|
||||||
}
|
}
|
||||||
priv := &rsa.PrivateKey{
|
priv := &rsa.PrivateKey{
|
||||||
PublicKey: rsa.PublicKey{
|
PublicKey: rsa.PublicKey{
|
||||||
@@ -270,6 +292,9 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||||
|
}
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
|
|
||||||
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||||
@@ -336,6 +361,9 @@ func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
||||||
|
}
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -393,8 +421,8 @@ func parseRSACert(req []byte) (*AddedKey, error) {
|
|||||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rsaPub.E.BitLen() > 30 {
|
if err := checkRSAKeyParams(rsaPub.N, rsaPub.E, k.P, k.Q); err != nil {
|
||||||
return nil, errors.New("agent: RSA public exponent too large")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
priv := rsa.PrivateKey{
|
priv := rsa.PrivateKey{
|
||||||
|
|||||||
+17
-1
@@ -348,6 +348,9 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
|||||||
if cert.CertType != HostCert {
|
if cert.CertType != HostCert {
|
||||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||||
}
|
}
|
||||||
|
if c.IsHostAuthority == nil {
|
||||||
|
return errors.New("ssh: cannot verify certificate, IsHostAuthority not set")
|
||||||
|
}
|
||||||
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||||
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||||
}
|
}
|
||||||
@@ -375,6 +378,9 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||||||
if cert.CertType != UserCert {
|
if cert.CertType != UserCert {
|
||||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||||
}
|
}
|
||||||
|
if c.IsUserAuthority == nil {
|
||||||
|
return nil, errors.New("ssh: cannot verify certificate, IsUserAuthority not set")
|
||||||
|
}
|
||||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||||
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||||
}
|
}
|
||||||
@@ -438,7 +444,17 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|||||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||||
return fmt.Errorf("ssh: cert has expired")
|
return fmt.Errorf("ssh: cert has expired")
|
||||||
}
|
}
|
||||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
// Match OpenSSH: the SK user-presence flag is never enforced on a
|
||||||
|
// certificate's CA signature. OpenSSH calls sshkey_verify with
|
||||||
|
// detailsp==NULL in sshkey.c:cert_parse, so the UP/UV flags are
|
||||||
|
// not even extracted. The UP bit on a CA signature reflects the
|
||||||
|
// CA operator's presence at signing time, which has no bearing on
|
||||||
|
// whether the user being authenticated is present now; enforcing
|
||||||
|
// it here would only break interop with certificates issued by
|
||||||
|
// non-interactive SK CAs. skKeyWithoutUP is a no-op for non-SK
|
||||||
|
// keys (the common case).
|
||||||
|
caKey := skKeyWithoutUP(cert.SignatureKey)
|
||||||
|
if err := caKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-6
@@ -11,6 +11,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -131,11 +132,17 @@ func (r RejectionReason) String() string {
|
|||||||
return fmt.Sprintf("unknown reason %d", int(r))
|
return fmt.Sprintf("unknown reason %d", int(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a uint32, b int) uint32 {
|
// minPayloadSize returns min(limit, length) clamped to a uint32. It is used
|
||||||
if a < uint32(b) {
|
// to compute the size of the next channel data packet from the remaining
|
||||||
return a
|
// payload. The comparison is done in int64 because length is an int — on
|
||||||
|
// 64-bit systems len(data) can exceed 2^32, and a direct uint32(length)
|
||||||
|
// cast would silently truncate to 0 at every multiple of 2^32, causing
|
||||||
|
// WriteExtended's loop to spin without making progress.
|
||||||
|
func minPayloadSize(limit uint32, length int) uint32 {
|
||||||
|
if int64(length) > int64(limit) {
|
||||||
|
return limit
|
||||||
}
|
}
|
||||||
return uint32(b)
|
return uint32(length)
|
||||||
}
|
}
|
||||||
|
|
||||||
type channelDirection uint8
|
type channelDirection uint8
|
||||||
@@ -177,6 +184,12 @@ type channel struct {
|
|||||||
// with WantReply=true outstanding. This lock is held by a
|
// with WantReply=true outstanding. This lock is held by a
|
||||||
// goroutine that has such an outgoing request pending.
|
// goroutine that has such an outgoing request pending.
|
||||||
sentRequestMu sync.Mutex
|
sentRequestMu sync.Mutex
|
||||||
|
// sentRequestPending is set to true while a SendRequest call with
|
||||||
|
// WantReply=true is in flight. handlePacket uses it as a gate: responses
|
||||||
|
// arriving while no request is pending are dropped to prevent a
|
||||||
|
// misbehaving peer from stalling the mux read loop by filling ch.msg
|
||||||
|
// with unsolicited channelRequestSuccess/Failure messages.
|
||||||
|
sentRequestPending atomic.Bool
|
||||||
|
|
||||||
incomingRequests chan *Request
|
incomingRequests chan *Request
|
||||||
|
|
||||||
@@ -251,7 +264,7 @@ func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err e
|
|||||||
ch.writeMu.Unlock()
|
ch.writeMu.Unlock()
|
||||||
|
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
space := min(ch.maxRemotePayload, len(data))
|
space := minPayloadSize(ch.maxRemotePayload, len(data))
|
||||||
if space, err = ch.remoteWin.reserve(space); err != nil {
|
if space, err = ch.remoteWin.reserve(space); err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
@@ -460,6 +473,18 @@ func (ch *channel) handlePacket(packet []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch.incomingRequests <- &req
|
ch.incomingRequests <- &req
|
||||||
|
case *channelRequestSuccessMsg, *channelRequestFailureMsg:
|
||||||
|
// Drop responses that arrive when no SendRequest is waiting, to
|
||||||
|
// prevent a malicious peer from filling ch.msg and stalling the
|
||||||
|
// mux read loop. The non-blocking send additionally protects the
|
||||||
|
// loop if a well-behaved caller is slow to read.
|
||||||
|
if !ch.sentRequestPending.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case ch.msg <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ch.msg <- msg
|
ch.msg <- msg
|
||||||
}
|
}
|
||||||
@@ -530,7 +555,17 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
|
|||||||
Language: "en",
|
Language: "en",
|
||||||
}
|
}
|
||||||
ch.decided = true
|
ch.decided = true
|
||||||
return ch.sendMessage(reject)
|
err := ch.sendMessage(reject)
|
||||||
|
|
||||||
|
// Remove the channel from the mux to prevent memory leaks.
|
||||||
|
// Do not call ch.close() here: no goroutine holds a reference to a
|
||||||
|
// rejected channel's internal channels (msg, incomingRequests), so
|
||||||
|
// removing it from chanList is sufficient for GC. Calling close()
|
||||||
|
// would race with the mux loop goroutine (handlePacket or dropAll),
|
||||||
|
// causing a panic from closing an already-closed channel.
|
||||||
|
ch.mux.chanList.remove(ch.localId)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *channel) Read(data []byte) (int, error) {
|
func (ch *channel) Read(data []byte) (int, error) {
|
||||||
@@ -586,6 +621,27 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
|
|||||||
if wantReply {
|
if wantReply {
|
||||||
ch.sentRequestMu.Lock()
|
ch.sentRequestMu.Lock()
|
||||||
defer ch.sentRequestMu.Unlock()
|
defer ch.sentRequestMu.Unlock()
|
||||||
|
|
||||||
|
// Open the gate so that responses arriving while this request is in
|
||||||
|
// flight are allowed to reach ch.msg. Responses arriving while no
|
||||||
|
// request is pending are dropped by handlePacket.
|
||||||
|
ch.sentRequestPending.Store(true)
|
||||||
|
defer ch.sentRequestPending.Store(false)
|
||||||
|
|
||||||
|
// Drain any spurious responses that may have been buffered. This
|
||||||
|
// prevents a previously buffered unexpected response from being
|
||||||
|
// consumed instead of the actual response for this request.
|
||||||
|
drain:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch.msg:
|
||||||
|
if !ok {
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := channelRequestMsg{
|
msg := channelRequestMsg{
|
||||||
|
|||||||
+1
-1
@@ -407,7 +407,7 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
|
|||||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(padding+1) >= len(plain) {
|
if int(padding)+1 >= len(plain) {
|
||||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||||
}
|
}
|
||||||
plain = plain[1 : length-uint32(padding)]
|
plain = plain[1 : length-uint32(padding)]
|
||||||
|
|||||||
+85
@@ -88,6 +88,32 @@ func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan
|
|||||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewControlClientConn establishes an SSH connection over an OpenSSH
|
||||||
|
// ControlMaster socket c in proxy mode.
|
||||||
|
//
|
||||||
|
// Note that this package only implements the client side of the multiplexing
|
||||||
|
// protocol. The provided net.Conn must be a local, secure connection (such as a
|
||||||
|
// Unix domain socket) connected to an already-running OpenSSH process acting as
|
||||||
|
// the ControlMaster.
|
||||||
|
//
|
||||||
|
// WARNING: Because proxy mode bypasses the standard cryptographic handshake
|
||||||
|
// passing a standard network connection (e.g., TCP) will result in plaintext
|
||||||
|
// data leakage.
|
||||||
|
//
|
||||||
|
// The Request and NewChannel channels must be serviced or the connection
|
||||||
|
// will hang.
|
||||||
|
func NewControlClientConn(c net.Conn) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||||
|
conn := &connection{
|
||||||
|
sshConn: sshConn{conn: c},
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if conn.transport, err = handshakeControlProxy(c); err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("ssh: control proxy handshake failed: %w", err)
|
||||||
|
}
|
||||||
|
conn.mux = newMux(conn.transport)
|
||||||
|
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||||
|
}
|
||||||
|
|
||||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
||||||
// 7.
|
// 7.
|
||||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
||||||
@@ -197,6 +223,59 @@ type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
|||||||
// the server. A BannerCallback receives the message sent by the remote server.
|
// the server. A BannerCallback receives the message sent by the remote server.
|
||||||
type BannerCallback func(message string) error
|
type BannerCallback func(message string) error
|
||||||
|
|
||||||
|
// ClientAuthContext contains information about the current state of the
|
||||||
|
// authentication process, passed to [ClientAuthCallback].
|
||||||
|
type ClientAuthContext struct {
|
||||||
|
// Metadata contains the connection metadata.
|
||||||
|
Metadata ConnMetadata
|
||||||
|
|
||||||
|
// Algorithms contains the negotiated algorithms.
|
||||||
|
Algorithms NegotiatedAlgorithms
|
||||||
|
|
||||||
|
// AllowedMethods lists the authentication methods currently accepted
|
||||||
|
// by the server. These are the protocol-level names defined in RFC 4252
|
||||||
|
// such as "publickey", "password".
|
||||||
|
AllowedMethods []string
|
||||||
|
|
||||||
|
// PartialSuccessMethods lists the authentication methods that have already
|
||||||
|
// succeeded, indicating a multi-step authentication flow. This list
|
||||||
|
// represents the exact sequence of partial successes and may contain
|
||||||
|
// duplicates if the same method succeeded multiple times.
|
||||||
|
PartialSuccessMethods []string
|
||||||
|
|
||||||
|
// TriedMethods lists the methods that have already been attempted and
|
||||||
|
// failed during this session. This list represents the exact sequence of
|
||||||
|
// failures and may contain duplicates. This allows the callback to also
|
||||||
|
// track the number of failed attempts for a specific method.
|
||||||
|
TriedMethods []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientAuthCallback is a hook invoked before each authentication attempt. It
|
||||||
|
// allows the client to dynamically select an authentication method based on the
|
||||||
|
// current context, server capabilities, or previous failures.
|
||||||
|
//
|
||||||
|
// The callback is invoked after the initial "none" authentication method, once
|
||||||
|
// the server's supported authentication methods are known.
|
||||||
|
//
|
||||||
|
// Return values:
|
||||||
|
// - (AuthMethod, nil): The client will attempt this specific method next.
|
||||||
|
// The returned method does NOT need to be present in [ClientConfig.Auth].
|
||||||
|
// This allows for dynamic authentication strategies (e.g., prompting
|
||||||
|
// for a password only if public key auth fails). Callers should inspect
|
||||||
|
// [ClientAuthContext.TriedMethods] to avoid repeatedly returning the
|
||||||
|
// same failing method.
|
||||||
|
// - (nil, nil): The client selects from [ClientConfig.Auth] the first
|
||||||
|
// instance of a method that has not been tried yet, or aborts if none
|
||||||
|
// are left. If authentication is not successful, the callback is invoked
|
||||||
|
// again before the following attempt.
|
||||||
|
// - (nil, error): The authentication process is aborted immediately,
|
||||||
|
// causing the ongoing SSH handshake to fail with the provided error.
|
||||||
|
//
|
||||||
|
// To bound resource use, the client caps the total number of authentication
|
||||||
|
// attempts (failures and partial successes combined) at 64. If the cap is
|
||||||
|
// exceeded the handshake aborts with an error.
|
||||||
|
type ClientAuthCallback func(ctx *ClientAuthContext) (AuthMethod, error)
|
||||||
|
|
||||||
// A ClientConfig structure is used to configure a Client. It must not be
|
// A ClientConfig structure is used to configure a Client. It must not be
|
||||||
// modified after having been passed to an SSH function.
|
// modified after having been passed to an SSH function.
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
@@ -210,6 +289,9 @@ type ClientConfig struct {
|
|||||||
// Auth contains possible authentication methods to use with the
|
// Auth contains possible authentication methods to use with the
|
||||||
// server. Only the first instance of a particular RFC 4252 method will
|
// server. Only the first instance of a particular RFC 4252 method will
|
||||||
// be used during authentication.
|
// be used during authentication.
|
||||||
|
//
|
||||||
|
// If AuthCallback is set, these AuthMethod are only used if the
|
||||||
|
// callback returns nil.
|
||||||
Auth []AuthMethod
|
Auth []AuthMethod
|
||||||
|
|
||||||
// HostKeyCallback is called during the cryptographic
|
// HostKeyCallback is called during the cryptographic
|
||||||
@@ -240,6 +322,9 @@ type ClientConfig struct {
|
|||||||
//
|
//
|
||||||
// A Timeout of zero means no timeout.
|
// A Timeout of zero means no timeout.
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// AuthCallback, if non-nil, is invoked before each authentication attempt.
|
||||||
|
AuthCallback ClientAuthCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsecureIgnoreHostKey returns a function that can be used for
|
// InsecureIgnoreHostKey returns a function that can be used for
|
||||||
|
|||||||
+50
-14
@@ -21,6 +21,12 @@ const (
|
|||||||
authSuccess
|
authSuccess
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxAuthClientTried bounds the total number of authentication attempts
|
||||||
|
// (failures and partial successes combined) the client makes before
|
||||||
|
// aborting the loop, to prevent unbounded growth when an AuthCallback
|
||||||
|
// keeps supplying methods.
|
||||||
|
const maxAuthClientTried = 64
|
||||||
|
|
||||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||||
// initiate user auth session
|
// initiate user auth session
|
||||||
@@ -67,32 +73,62 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|||||||
// then any untried methods suggested by the server.
|
// then any untried methods suggested by the server.
|
||||||
var tried []string
|
var tried []string
|
||||||
var lastMethods []string
|
var lastMethods []string
|
||||||
|
var partialSuccess []string
|
||||||
|
|
||||||
sessionID := c.transport.getSessionID()
|
sessionID := c.transport.getSessionID()
|
||||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||||
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
|
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// On disconnect, return error immediately
|
// On disconnect, return error immediately
|
||||||
if _, ok := err.(*disconnectMsg); ok {
|
if _, isDisconnect := err.(*disconnectMsg); isDisconnect {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// We return the error later if there is no other method left to
|
// We return the error later if there is no other method
|
||||||
// try.
|
// left to try.
|
||||||
ok = authFailure
|
ok = authFailure
|
||||||
}
|
}
|
||||||
if ok == authSuccess {
|
|
||||||
// success
|
switch ok {
|
||||||
|
case authSuccess:
|
||||||
return nil
|
return nil
|
||||||
} else if ok == authFailure {
|
case authPartialSuccess:
|
||||||
if m := auth.method(); !slices.Contains(tried, m) {
|
partialSuccess = append(partialSuccess, auth.method())
|
||||||
tried = append(tried, m)
|
case authFailure:
|
||||||
}
|
tried = append(tried, auth.method())
|
||||||
}
|
}
|
||||||
|
if len(partialSuccess)+len(tried) > maxAuthClientTried {
|
||||||
|
return fmt.Errorf("ssh: too many authentication attempts (%d), aborting",
|
||||||
|
len(partialSuccess)+len(tried))
|
||||||
|
}
|
||||||
|
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = lastMethods
|
methods = lastMethods
|
||||||
}
|
}
|
||||||
lastMethods = methods
|
lastMethods = methods
|
||||||
|
|
||||||
|
// If AuthCallback is set it takes precedence: it picks the next
|
||||||
|
// AuthMethod dynamically. The returned method need not be in
|
||||||
|
// config.Auth. If the callback returns (nil, nil) we fall back to
|
||||||
|
// selecting the next untried method from config.Auth below; on
|
||||||
|
// (nil, error) the handshake aborts.
|
||||||
|
if config.AuthCallback != nil {
|
||||||
|
ctx := &ClientAuthContext{
|
||||||
|
Metadata: c,
|
||||||
|
Algorithms: c.Algorithms(),
|
||||||
|
AllowedMethods: slices.Clone(methods),
|
||||||
|
PartialSuccessMethods: slices.Clone(partialSuccess),
|
||||||
|
TriedMethods: slices.Clone(tried),
|
||||||
|
}
|
||||||
|
altAuth, cbErr := config.AuthCallback(ctx)
|
||||||
|
if cbErr != nil {
|
||||||
|
return cbErr
|
||||||
|
}
|
||||||
|
if altAuth != nil {
|
||||||
|
auth = altAuth
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auth = nil
|
auth = nil
|
||||||
|
|
||||||
findNext:
|
findNext:
|
||||||
@@ -377,11 +413,11 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
|||||||
return authFailure, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If authentication succeeds or the list of available methods does not
|
// If authentication succeeds or partially succeeds, return immediately
|
||||||
// contain the "publickey" method, do not attempt to authenticate with any
|
// so the caller can select the next auth method. According to RFC 4252
|
||||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
// Section 7, if the server no longer lists "publickey" among its
|
||||||
// additional authentication methods are required.
|
// allowed methods, do not attempt to authenticate with any other keys.
|
||||||
if success == authSuccess || !slices.Contains(methods, cb.method()) {
|
if success == authSuccess || success == authPartialSuccess || !slices.Contains(methods, cb.method()) {
|
||||||
return success, methods, err
|
return success, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -91,9 +91,17 @@ func DiscardRequests(in <-chan *Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A connTransport represents the transport for a connection.
|
||||||
|
type connTransport interface {
|
||||||
|
packetConn
|
||||||
|
getAlgorithms() NegotiatedAlgorithms
|
||||||
|
getSessionID() []byte
|
||||||
|
waitSession() error
|
||||||
|
}
|
||||||
|
|
||||||
// A connection represents an incoming connection.
|
// A connection represents an incoming connection.
|
||||||
type connection struct {
|
type connection struct {
|
||||||
transport *handshakeTransport
|
transport connTransport
|
||||||
sshConn
|
sshConn
|
||||||
|
|
||||||
// The connection protocol.
|
// The connection protocol.
|
||||||
|
|||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
muxProtocolVersion = 4
|
||||||
|
|
||||||
|
muxMsgHello = 0x00000001
|
||||||
|
muxCProxy = 0x1000000f
|
||||||
|
muxSProxy = 0x8000000f
|
||||||
|
)
|
||||||
|
|
||||||
|
const controlProxyRequestID = 0
|
||||||
|
|
||||||
|
// handshakeControlProxy attempts to establish a transport connection with an
|
||||||
|
// OpenSSH ControlMaster socket in proxy mode. For details see:
|
||||||
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.mux
|
||||||
|
func handshakeControlProxy(rw io.ReadWriteCloser) (connTransport, error) {
|
||||||
|
if err := controlProxyWritePacket(rw, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddUint32(muxMsgHello)
|
||||||
|
b.AddUint32(muxProtocolVersion)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("mux hello write failed: %w", err)
|
||||||
|
}
|
||||||
|
if err := controlProxyWritePacket(rw, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddUint32(muxCProxy)
|
||||||
|
b.AddUint32(controlProxyRequestID)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("mux client proxy write failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
messageType, body, err := controlProxyReadMessage(rw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("mux hello read failed: %w", err)
|
||||||
|
}
|
||||||
|
if messageType != muxMsgHello {
|
||||||
|
return nil, fmt.Errorf("expected hello response, got %v", messageType)
|
||||||
|
}
|
||||||
|
var v uint32
|
||||||
|
if !body.ReadUint32(&v) {
|
||||||
|
return nil, errors.New("EOF reading mux protocol version")
|
||||||
|
}
|
||||||
|
if v != muxProtocolVersion {
|
||||||
|
return nil, fmt.Errorf("mux server has unsupported version %v", v)
|
||||||
|
}
|
||||||
|
messageType, body, err = controlProxyReadMessage(rw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("mux server proxy read failed: %w", err)
|
||||||
|
}
|
||||||
|
if messageType != muxSProxy {
|
||||||
|
return nil, fmt.Errorf("expected server proxy response, got %v", messageType)
|
||||||
|
}
|
||||||
|
var reqID uint32
|
||||||
|
if !body.ReadUint32(&reqID) {
|
||||||
|
return nil, errors.New("EOF reading request id")
|
||||||
|
}
|
||||||
|
if reqID != controlProxyRequestID {
|
||||||
|
return nil, fmt.Errorf("expected request id %v, got %v", controlProxyRequestID, reqID)
|
||||||
|
}
|
||||||
|
return &controlProxyTransport{rw}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// controlProxyTransport implements the connTransport interface for
|
||||||
|
// ControlMaster connections. Each controlMessage has zero length padding and
|
||||||
|
// no MAC.
|
||||||
|
type controlProxyTransport struct {
|
||||||
|
rw io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) Close() error {
|
||||||
|
return p.rw.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) writePacket(controlMessage []byte) error {
|
||||||
|
return controlProxyWritePacket(p.rw, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddUint8(0) // Padding length.
|
||||||
|
b.AddBytes(controlMessage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) readPacket() ([]byte, error) {
|
||||||
|
buf, err := controlProxyReadPacket(p.rw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ssh: error reading control message: %w", err)
|
||||||
|
}
|
||||||
|
// Discard the padding length.
|
||||||
|
if len(buf) < 1 {
|
||||||
|
return nil, errors.New("ssh: EOF reading padding length")
|
||||||
|
}
|
||||||
|
if buf[0] != 0 {
|
||||||
|
return nil, errors.New("ssh: unexpected non-zero padding in control message")
|
||||||
|
}
|
||||||
|
return buf[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) getAlgorithms() NegotiatedAlgorithms {
|
||||||
|
return NegotiatedAlgorithms{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) getSessionID() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *controlProxyTransport) waitSession() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func controlProxyWritePacket(w io.Writer, f cryptobyte.BuilderContinuation) error {
|
||||||
|
var buf []byte
|
||||||
|
b := cryptobyte.NewBuilder(buf)
|
||||||
|
b.AddUint32LengthPrefixed(f)
|
||||||
|
out, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func controlProxyReadPacket(r io.Reader) (cryptobyte.String, error) {
|
||||||
|
var l uint32
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &l); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l > maxPacket {
|
||||||
|
return nil, fmt.Errorf("message length %v exceeds maximum %v", l, maxPacket)
|
||||||
|
}
|
||||||
|
buf := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func controlProxyReadMessage(r io.Reader) (messageType uint32, body cryptobyte.String, err error) {
|
||||||
|
body, err = controlProxyReadPacket(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("error reading message body: %w", err)
|
||||||
|
}
|
||||||
|
if !body.ReadUint32(&messageType) {
|
||||||
|
return 0, nil, errors.New("EOF reading message type")
|
||||||
|
}
|
||||||
|
return messageType, body, nil
|
||||||
|
}
|
||||||
+66
-9
@@ -16,6 +16,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
)
|
)
|
||||||
@@ -718,15 +719,9 @@ func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshak
|
|||||||
kexDHGexRequest.MaxBits, kexDHGexRequest.PreferredBits)
|
kexDHGexRequest.MaxBits, kexDHGexRequest.PreferredBits)
|
||||||
}
|
}
|
||||||
|
|
||||||
var p *big.Int
|
p, err := chooseDH(kexDHGexRequest)
|
||||||
// We hardcode sending Oakley Group 14 (2048 bits), Oakley Group 15 (3072
|
if err != nil {
|
||||||
// bits) or Oakley Group 16 (4096 bits), based on the requested max size.
|
return nil, err
|
||||||
if kexDHGexRequest.MaxBits < 3072 {
|
|
||||||
p, _ = new(big.Int).SetString(oakleyGroup14, 16)
|
|
||||||
} else if kexDHGexRequest.MaxBits < 4096 {
|
|
||||||
p, _ = new(big.Int).SetString(oakleyGroup15, 16)
|
|
||||||
} else {
|
|
||||||
p, _ = new(big.Int).SetString(oakleyGroup16, 16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g := big.NewInt(2)
|
g := big.NewInt(2)
|
||||||
@@ -805,3 +800,65 @@ func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshak
|
|||||||
Hash: gex.hashFunc,
|
Hash: gex.hashFunc,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dhKEXGroup struct {
|
||||||
|
size int
|
||||||
|
p *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// supportedDHKEXGroups returns the DH groups the server is willing to offer
|
||||||
|
// for diffie-hellman-group-exchange-* key exchanges. The list is built lazily
|
||||||
|
// on first use to keep the hex-to-big.Int parse out of package initialization.
|
||||||
|
var supportedDHKEXGroups = sync.OnceValue(func() []dhKEXGroup {
|
||||||
|
specs := []struct {
|
||||||
|
size int
|
||||||
|
hex string
|
||||||
|
}{
|
||||||
|
{2048, oakleyGroup14},
|
||||||
|
{3072, oakleyGroup15},
|
||||||
|
{4096, oakleyGroup16},
|
||||||
|
}
|
||||||
|
out := make([]dhKEXGroup, 0, len(specs))
|
||||||
|
for _, s := range specs {
|
||||||
|
p, _ := new(big.Int).SetString(s.hex, 16)
|
||||||
|
out = append(out, dhKEXGroup{size: s.size, p: p})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
})
|
||||||
|
|
||||||
|
// chooseDH picks a DH group for the given client request, mirroring the
|
||||||
|
// algorithm used by OpenSSH's choose_dh in dh.c: prefer the smallest known
|
||||||
|
// group larger than or equal to the client's PreferredBits, and otherwise pick
|
||||||
|
// the largest group within the accepted [MinBits, MaxBits] range.
|
||||||
|
func chooseDH(req kexDHGexRequestMsg) (*big.Int, error) {
|
||||||
|
var best *big.Int
|
||||||
|
bestSize := 0
|
||||||
|
wantBits := int(req.PreferredBits)
|
||||||
|
|
||||||
|
for _, group := range supportedDHKEXGroups() {
|
||||||
|
if uint32(group.size) < req.MinBits || uint32(group.size) > req.MaxBits {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bestSize == 0 {
|
||||||
|
best = group.p
|
||||||
|
bestSize = group.size
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
closerFromAbove := group.size >= wantBits && group.size < bestSize
|
||||||
|
closerFromBelow := group.size > bestSize && bestSize < wantBits
|
||||||
|
|
||||||
|
if closerFromAbove || closerFromBelow {
|
||||||
|
best = group.p
|
||||||
|
bestSize = group.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bestSize == 0 {
|
||||||
|
return nil, fmt.Errorf("ssh: no suitable DH group found for request min: %d, preferred: %d, max: %d",
|
||||||
|
req.MinBits, req.PreferredBits, req.MaxBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
return best, nil
|
||||||
|
}
|
||||||
|
|||||||
+96
-3
@@ -76,7 +76,7 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
|
|||||||
case InsecureKeyAlgoDSA:
|
case InsecureKeyAlgoDSA:
|
||||||
return parseDSA(in)
|
return parseDSA(in)
|
||||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
||||||
return parseECDSA(in)
|
return parseECDSA(in, algo)
|
||||||
case KeyAlgoSKECDSA256:
|
case KeyAlgoSKECDSA256:
|
||||||
return parseSKECDSA(in)
|
return parseSKECDSA(in)
|
||||||
case KeyAlgoED25519:
|
case KeyAlgoED25519:
|
||||||
@@ -469,6 +469,12 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 8192 bits is also the maximum RSA key size accepted by crypto/tls for
|
||||||
|
// signature verification:
|
||||||
|
// https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
|
||||||
|
if w.N.BitLen() > 8192 {
|
||||||
|
return nil, nil, errors.New("ssh: rsa modulus too large")
|
||||||
|
}
|
||||||
if w.E.BitLen() > 24 {
|
if w.E.BitLen() > 24 {
|
||||||
return nil, nil, errors.New("ssh: exponent too large")
|
return nil, nil, errors.New("ssh: exponent too large")
|
||||||
}
|
}
|
||||||
@@ -574,6 +580,24 @@ func checkDSAParams(param *dsa.Parameters) error {
|
|||||||
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
|
||||||
|
// this to prevent DoS attacks where an attacker sends a huge Q which makes
|
||||||
|
// verification slow.
|
||||||
|
if l := param.Q.BitLen(); l != 160 {
|
||||||
|
return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generator G is an element of the group, so it must be strictly less
|
||||||
|
// than the modulus P.
|
||||||
|
if param.G.Cmp(param.P) >= 0 {
|
||||||
|
return errors.New("ssh: DSA generator larger than modulus")
|
||||||
|
}
|
||||||
|
|
||||||
|
// G must be positive.
|
||||||
|
if param.G.Sign() <= 0 {
|
||||||
|
return errors.New("ssh: DSA generator must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,6 +620,14 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The public value Y must be a non-zero element of the group, i.e.
|
||||||
|
// strictly between 0 and P. crypto/dsa.Verify does not range-check Y,
|
||||||
|
// so we reject out-of-range values here to prevent a maliciously
|
||||||
|
// oversized Y from slowing verification.
|
||||||
|
if w.Y.Sign() <= 0 || w.Y.Cmp(w.P) >= 0 {
|
||||||
|
return nil, nil, errors.New("ssh: DSA public value Y out of range")
|
||||||
|
}
|
||||||
|
|
||||||
key := &dsaPublicKey{
|
key := &dsaPublicKey{
|
||||||
Parameters: param,
|
Parameters: param,
|
||||||
Y: w.Y,
|
Y: w.Y,
|
||||||
@@ -774,7 +806,7 @@ func supportedEllipticCurve(curve elliptic.Curve) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
||||||
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
func parseECDSA(in []byte, expectedType string) (out PublicKey, rest []byte, err error) {
|
||||||
var w struct {
|
var w struct {
|
||||||
Curve string
|
Curve string
|
||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
@@ -785,6 +817,12 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actualType := "ecdsa-sha2-" + w.Curve
|
||||||
|
if expectedType != actualType {
|
||||||
|
return nil, nil, fmt.Errorf("ssh: algorithm type mismatch: expected %q, found curve %q (type %q)",
|
||||||
|
expectedType, w.Curve, actualType)
|
||||||
|
}
|
||||||
|
|
||||||
key := new(ecdsa.PublicKey)
|
key := new(ecdsa.PublicKey)
|
||||||
|
|
||||||
switch w.Curve {
|
switch w.Curve {
|
||||||
@@ -869,11 +907,25 @@ type skFields struct {
|
|||||||
Counter uint32
|
Counter uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flagUserPresence is the "user present" bit (UP) in the SK signature
|
||||||
|
// flags, matching the FIDO CTAP2 authenticatorData UP flag. See
|
||||||
|
// openssh/PROTOCOL.u2f.
|
||||||
|
const flagUserPresence = 0x01
|
||||||
|
|
||||||
|
// errSKMissingUserPresence is returned by SK key Verify methods when
|
||||||
|
// the signature does not assert user presence and the key was not
|
||||||
|
// marked as no-touch-required.
|
||||||
|
var errSKMissingUserPresence = errors.New("ssh: signature missing required user presence flag")
|
||||||
|
|
||||||
type skECDSAPublicKey struct {
|
type skECDSAPublicKey struct {
|
||||||
// application is a URL-like string, typically "ssh:" for SSH.
|
// application is a URL-like string, typically "ssh:" for SSH.
|
||||||
// see openssh/PROTOCOL.u2f for details.
|
// see openssh/PROTOCOL.u2f for details.
|
||||||
application string
|
application string
|
||||||
ecdsa.PublicKey
|
ecdsa.PublicKey
|
||||||
|
// noTouchRequired, when true, disables the default user-presence
|
||||||
|
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||||
|
// key, never on an instance shared across authentication attempts.
|
||||||
|
noTouchRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *skECDSAPublicKey) Type() string {
|
func (k *skECDSAPublicKey) Type() string {
|
||||||
@@ -959,6 +1011,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||||
|
return errSKMissingUserPresence
|
||||||
|
}
|
||||||
|
|
||||||
blob := struct {
|
blob := struct {
|
||||||
ApplicationDigest []byte `ssh:"rest"`
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
Flags byte
|
Flags byte
|
||||||
@@ -992,6 +1048,10 @@ type skEd25519PublicKey struct {
|
|||||||
// see openssh/PROTOCOL.u2f for details.
|
// see openssh/PROTOCOL.u2f for details.
|
||||||
application string
|
application string
|
||||||
ed25519.PublicKey
|
ed25519.PublicKey
|
||||||
|
// noTouchRequired, when true, disables the default user-presence
|
||||||
|
// check in Verify. It is set by skKeyWithoutUP on a clone of the
|
||||||
|
// key, never on an instance shared across authentication attempts.
|
||||||
|
noTouchRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *skEd25519PublicKey) Type() string {
|
func (k *skEd25519PublicKey) Type() string {
|
||||||
@@ -1066,6 +1126,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
|
||||||
|
return errSKMissingUserPresence
|
||||||
|
}
|
||||||
|
|
||||||
blob := struct {
|
blob := struct {
|
||||||
ApplicationDigest []byte `ssh:"rest"`
|
ApplicationDigest []byte `ssh:"rest"`
|
||||||
Flags byte
|
Flags byte
|
||||||
@@ -1408,6 +1472,17 @@ func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenSSH does not impose an upper bound on the bcrypt round count
|
||||||
|
// stored in the key file, but bcrypt_pbkdf cost is linear in rounds:
|
||||||
|
// the default is 16, ssh-keygen lets users pick anything up to
|
||||||
|
// INT_MAX. Cap at 2048 (128x the default, a few seconds of CPU) so
|
||||||
|
// that an oversized value in the file cannot tie up the caller for
|
||||||
|
// months.
|
||||||
|
const maxRounds = 1 << 11
|
||||||
|
if opts.Rounds > maxRounds {
|
||||||
|
return nil, fmt.Errorf("ssh: bcrypt KDF rounds %d exceed maximum %d", opts.Rounds, maxRounds)
|
||||||
|
}
|
||||||
|
|
||||||
k, err := bcrypt_pbkdf.Key(passphrase, []byte(opts.Salt), int(opts.Rounds), 32+16)
|
k, err := bcrypt_pbkdf.Key(passphrase, []byte(opts.Salt), int(opts.Rounds), 32+16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1577,10 +1652,28 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirror the validation done in parseRSA for public keys: cap the
|
||||||
|
// modulus at the same limit enforced by crypto/tls, reject oversized
|
||||||
|
// or invalid exponents, and additionally bound the prime factors to
|
||||||
|
// avoid the expensive CRT coefficient recomputation in pk.Precompute.
|
||||||
|
if key.N.BitLen() > 8192 {
|
||||||
|
return nil, errors.New("ssh: rsa modulus too large")
|
||||||
|
}
|
||||||
|
if key.P.BitLen() > 4096 || key.Q.BitLen() > 4096 {
|
||||||
|
return nil, errors.New("ssh: rsa prime too large")
|
||||||
|
}
|
||||||
|
if key.E.BitLen() > 24 {
|
||||||
|
return nil, errors.New("ssh: exponent too large")
|
||||||
|
}
|
||||||
|
e := key.E.Int64()
|
||||||
|
if e < 3 || e&1 == 0 {
|
||||||
|
return nil, errors.New("ssh: incorrect exponent")
|
||||||
|
}
|
||||||
|
|
||||||
pk := &rsa.PrivateKey{
|
pk := &rsa.PrivateKey{
|
||||||
PublicKey: rsa.PublicKey{
|
PublicKey: rsa.PublicKey{
|
||||||
N: key.N,
|
N: key.N,
|
||||||
E: int(key.E.Int64()),
|
E: int(e),
|
||||||
},
|
},
|
||||||
D: key.D,
|
D: key.D,
|
||||||
Primes: []*big.Int{key.P, key.Q},
|
Primes: []*big.Int{key.P, key.Q},
|
||||||
|
|||||||
+27
-6
@@ -160,8 +160,13 @@ func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool
|
|||||||
|
|
||||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||||
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||||
_, ok := db.revoked[string(key.Marshal())]
|
if _, ok := db.revoked[string(key.Marshal())]; ok {
|
||||||
return ok
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const markerCert = "@cert-authority"
|
const markerCert = "@cert-authority"
|
||||||
@@ -173,7 +178,7 @@ func nextWord(line []byte) (string, []byte) {
|
|||||||
return string(line), nil
|
return string(line), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(line[:i]), bytes.TrimSpace(line[i:])
|
return string(line[:i]), trimSpace(line[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
|
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
|
||||||
@@ -183,12 +188,17 @@ func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
host, line = nextWord(line)
|
host, line = nextWord(line)
|
||||||
|
// If the extracted 'host' starts with '@', it means we either encountered
|
||||||
|
// a second marker (e.g., "@cert-authority @revoked") or an unknown marker
|
||||||
|
// (e.g., "@unknown"). Both are invalid.
|
||||||
|
if len(host) > 0 && host[0] == '@' {
|
||||||
|
return "", "", nil, fmt.Errorf("knownhosts: unexpected marker: %q", host)
|
||||||
|
}
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return "", "", nil, errors.New("knownhosts: missing host pattern")
|
return "", "", nil, errors.New("knownhosts: missing host pattern")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore the keytype as it's in the key blob anyway.
|
wantType, line := nextWord(line)
|
||||||
_, line = nextWord(line)
|
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return "", "", nil, errors.New("knownhosts: missing key type pattern")
|
return "", "", nil, errors.New("knownhosts: missing key type pattern")
|
||||||
}
|
}
|
||||||
@@ -204,6 +214,10 @@ func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error)
|
|||||||
return "", "", nil, err
|
return "", "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.Type() != wantType {
|
||||||
|
return "", "", nil, fmt.Errorf("knownhosts: key type mismatch: found %q, want %q", key.Type(), wantType)
|
||||||
|
}
|
||||||
|
|
||||||
return marker, host, key, nil
|
return marker, host, key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +396,7 @@ func (db *hostKeyDB) Read(r io.Reader, filename string) error {
|
|||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
lineNum++
|
lineNum++
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
line = bytes.TrimSpace(line)
|
line = trimSpace(line)
|
||||||
if len(line) == 0 || line[0] == '#' {
|
if len(line) == 0 || line[0] == '#' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -530,3 +544,10 @@ func newHashedHost(encoded string) (*hashedHost, error) {
|
|||||||
func (h *hashedHost) match(a addr) bool {
|
func (h *hashedHost) match(a addr) bool {
|
||||||
return bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash)
|
return bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trimSpace removes leading and trailing ASCII whitespace (space and tab). It
|
||||||
|
// is used instead of bytes.TrimSpace to match OpenSSH behavior, which strictly
|
||||||
|
// parses only ASCII space (0x20) and tab (0x09) as whitespace.
|
||||||
|
func trimSpace(in []byte) []byte {
|
||||||
|
return bytes.Trim(in, " \t")
|
||||||
|
}
|
||||||
|
|||||||
+35
-4
@@ -91,9 +91,10 @@ type mux struct {
|
|||||||
|
|
||||||
incomingChannels chan NewChannel
|
incomingChannels chan NewChannel
|
||||||
|
|
||||||
globalSentMu sync.Mutex
|
globalSentMu sync.Mutex
|
||||||
globalResponses chan interface{}
|
globalSentPending atomic.Bool
|
||||||
incomingRequests chan *Request
|
globalResponses chan interface{}
|
||||||
|
incomingRequests chan *Request
|
||||||
|
|
||||||
errCond *sync.Cond
|
errCond *sync.Cond
|
||||||
err error
|
err error
|
||||||
@@ -141,6 +142,27 @@ func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []
|
|||||||
if wantReply {
|
if wantReply {
|
||||||
m.globalSentMu.Lock()
|
m.globalSentMu.Lock()
|
||||||
defer m.globalSentMu.Unlock()
|
defer m.globalSentMu.Unlock()
|
||||||
|
|
||||||
|
// Open the gate so that responses arriving while this request is in
|
||||||
|
// flight are allowed to reach globalResponses. Any response arriving
|
||||||
|
// while no request is pending is dropped by handleGlobalPacket.
|
||||||
|
m.globalSentPending.Store(true)
|
||||||
|
defer m.globalSentPending.Store(false)
|
||||||
|
|
||||||
|
// Drain any spurious responses that may have been buffered. This prevents
|
||||||
|
// a previously buffered unexpected response from being consumed instead
|
||||||
|
// of the actual response for this request.
|
||||||
|
drain:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-m.globalResponses:
|
||||||
|
if !ok {
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break drain
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.sendMessage(globalRequestMsg{
|
if err := m.sendMessage(globalRequestMsg{
|
||||||
@@ -267,7 +289,16 @@ func (m *mux) handleGlobalPacket(packet []byte) error {
|
|||||||
mux: m,
|
mux: m,
|
||||||
}
|
}
|
||||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||||
m.globalResponses <- msg
|
// Drop responses that arrive when no SendRequest is waiting, to
|
||||||
|
// prevent a malicious peer from staging responses for a future
|
||||||
|
// caller.
|
||||||
|
if !m.globalSentPending.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case m.globalResponses <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("not a global message %#v", msg))
|
panic(fmt.Sprintf("not a global message %#v", msg))
|
||||||
}
|
}
|
||||||
|
|||||||
+147
-18
@@ -34,21 +34,29 @@ type Permissions struct {
|
|||||||
// or not supported.
|
// or not supported.
|
||||||
CriticalOptions map[string]string
|
CriticalOptions map[string]string
|
||||||
|
|
||||||
// Extensions are extra functionality that the server may
|
// Extensions are extra functionality that the server may offer on
|
||||||
// offer on authenticated connections. Lack of support for an
|
// authenticated connections. Lack of support for an extension does not
|
||||||
// extension does not preclude authenticating a user. Common
|
// preclude authenticating a user. Common extensions are
|
||||||
// extensions are "permit-agent-forwarding",
|
// "permit-agent-forwarding", "permit-X11-forwarding". In general the Go
|
||||||
// "permit-X11-forwarding". The Go SSH library currently does
|
// SSH library does not act on extensions and it is up to server
|
||||||
// not act on any extension, and it is up to server
|
// implementations to honor them; extensions can also be used to pass data
|
||||||
// implementations to honor them. Extensions can be used to
|
// from the authentication callbacks to the server application layer.
|
||||||
// pass data from the authentication callbacks to the server
|
//
|
||||||
// application layer.
|
// The one extension acted upon by this library is "no-touch-required",
|
||||||
|
// which applies only to security-key public keys
|
||||||
|
// (sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com).
|
||||||
|
// When present, it waives the default requirement that SK signatures
|
||||||
|
// assert user presence (i.e. a physical touch of the authenticator)
|
||||||
|
// during signature verification.
|
||||||
Extensions map[string]string
|
Extensions map[string]string
|
||||||
|
|
||||||
// ExtraData allows to store user defined data.
|
// ExtraData allows to store user defined data.
|
||||||
ExtraData map[any]any
|
ExtraData map[any]any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GSSAPIWithMICConfig includes the server callbacks for gssapi-with-mic
|
||||||
|
// authentication. If either field is nil, gssapi-with-mic is considered not
|
||||||
|
// configured.
|
||||||
type GSSAPIWithMICConfig struct {
|
type GSSAPIWithMICConfig struct {
|
||||||
// AllowLogin, must be set, is called when gssapi-with-mic
|
// AllowLogin, must be set, is called when gssapi-with-mic
|
||||||
// authentication is selected (RFC 4462 section 3). The srcName is from the
|
// authentication is selected (RFC 4462 section 3). The srcName is from the
|
||||||
@@ -63,6 +71,10 @@ type GSSAPIWithMICConfig struct {
|
|||||||
Server GSSAPIServer
|
Server GSSAPIServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gssapiWithMICConfigured(config *GSSAPIWithMICConfig) bool {
|
||||||
|
return config != nil && config.AllowLogin != nil && config.Server != nil
|
||||||
|
}
|
||||||
|
|
||||||
// SendAuthBanner implements [ServerPreAuthConn].
|
// SendAuthBanner implements [ServerPreAuthConn].
|
||||||
func (s *connection) SendAuthBanner(msg string) error {
|
func (s *connection) SendAuthBanner(msg string) error {
|
||||||
return s.transport.writePacket(Marshal(&userAuthBannerMsg{
|
return s.transport.writePacket(Marshal(&userAuthBannerMsg{
|
||||||
@@ -84,6 +96,79 @@ type ServerPreAuthConn interface {
|
|||||||
SendAuthBanner(string) error
|
SendAuthBanner(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noTouchRequiredExtension is the extension name used by OpenSSH in
|
||||||
|
// authorized_keys options and certificate extensions to mark keys
|
||||||
|
// whose signatures do not need to assert user presence (touch). See
|
||||||
|
// ssh-keygen(1) and sshd(8).
|
||||||
|
const noTouchRequiredExtension = "no-touch-required"
|
||||||
|
|
||||||
|
// noTouchAllowed reports whether the user presence requirement on
|
||||||
|
// SK signatures should be waived for this authentication attempt. The
|
||||||
|
// requirement is waived when the "no-touch-required" extension is
|
||||||
|
// present either in the Permissions returned by the auth callback
|
||||||
|
// (authorized_keys-level opt-out) or in the certificate's own
|
||||||
|
// Extensions (CA-level opt-out), matching OpenSSH behavior. OpenSSH
|
||||||
|
// reads the per-key opt-out only from cert Extensions and
|
||||||
|
// authorized_keys options (never from CriticalOptions); we follow the
|
||||||
|
// same rule.
|
||||||
|
func noTouchAllowed(pubKey PublicKey, perms *Permissions) bool {
|
||||||
|
if perms != nil {
|
||||||
|
if _, ok := perms.Extensions[noTouchRequiredExtension]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cert, ok := pubKey.(*Certificate); ok {
|
||||||
|
if _, ok := cert.Extensions[noTouchRequiredExtension]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// skKeyWithoutUP returns a PublicKey equivalent to pubKey but whose
|
||||||
|
// Verify accepts SK signatures with the user-presence flag clear. If
|
||||||
|
// pubKey is not (and does not wrap) an SK key, pubKey is returned
|
||||||
|
// unchanged. The returned value never mutates pubKey: for SK keys a
|
||||||
|
// shallow copy is made so that the noTouchRequired flag is set only on
|
||||||
|
// the clone.
|
||||||
|
//
|
||||||
|
// The implementation is iterative rather than recursive. When pubKey
|
||||||
|
// is a *Certificate we unwrap exactly one level to look at the inner
|
||||||
|
// key. The SSH cert format forbids Certificate.Key from being another
|
||||||
|
// Certificate (parseCert rejects it), but nothing stops callers from
|
||||||
|
// constructing such a value directly in Go; a recursive descent could
|
||||||
|
// otherwise be driven to unbounded depth by a hand-crafted or cyclic
|
||||||
|
// Certificate. A malformed input of that shape simply returns
|
||||||
|
// unchanged here.
|
||||||
|
func skKeyWithoutUP(pubKey PublicKey) PublicKey {
|
||||||
|
cert, isCert := pubKey.(*Certificate)
|
||||||
|
target := pubKey
|
||||||
|
if isCert {
|
||||||
|
target = cert.Key
|
||||||
|
}
|
||||||
|
var cloned PublicKey
|
||||||
|
switch k := target.(type) {
|
||||||
|
case *skECDSAPublicKey:
|
||||||
|
c := *k
|
||||||
|
c.noTouchRequired = true
|
||||||
|
cloned = &c
|
||||||
|
case *skEd25519PublicKey:
|
||||||
|
c := *k
|
||||||
|
c.noTouchRequired = true
|
||||||
|
cloned = &c
|
||||||
|
default:
|
||||||
|
// Not an SK key (or a pathological *Certificate wrapping
|
||||||
|
// another *Certificate): pubKey is already usable for Verify.
|
||||||
|
return pubKey
|
||||||
|
}
|
||||||
|
if !isCert {
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
c := *cert
|
||||||
|
c.Key = cloned
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
// ServerConfig holds server specific configuration data.
|
// ServerConfig holds server specific configuration data.
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
// Config contains configuration shared between client and server.
|
// Config contains configuration shared between client and server.
|
||||||
@@ -242,8 +327,10 @@ func (c *pubKeyCache) add(candidate cachedPubKey) {
|
|||||||
type ServerConn struct {
|
type ServerConn struct {
|
||||||
Conn
|
Conn
|
||||||
|
|
||||||
// If the succeeding authentication callback returned a
|
// If the succeeding authentication callback returned a non-nil Permissions
|
||||||
// non-nil Permissions pointer, it is stored here.
|
// pointer, it is stored here. These are the permissions from the final,
|
||||||
|
// successful authentication method. Permissions returned by callbacks that
|
||||||
|
// return PartialSuccessError are not preserved and must be nil.
|
||||||
Permissions *Permissions
|
Permissions *Permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,8 +389,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil &&
|
if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil &&
|
||||||
config.KeyboardInteractiveCallback == nil && (config.GSSAPIWithMICConfig == nil ||
|
config.KeyboardInteractiveCallback == nil && !gssapiWithMICConfigured(config.GSSAPIWithMICConfig) {
|
||||||
config.GSSAPIWithMICConfig.AllowLogin == nil || config.GSSAPIWithMICConfig.Server == nil) {
|
|
||||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +613,15 @@ func (b *BannerError) Error() string {
|
|||||||
return b.Err.Error()
|
return b.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maxAuthServerAttempts caps the total number of SSH_MSG_USERAUTH_REQUEST
|
||||||
|
// messages the server will process on a single connection, regardless of
|
||||||
|
// outcome (failure, partial success, public key query, or none). It is a
|
||||||
|
// backstop against clients that drive the authentication loop indefinitely
|
||||||
|
// without ever incurring a real failure — for example by repeatedly
|
||||||
|
// triggering PartialSuccessError or by spamming public key offer queries —
|
||||||
|
// neither of which increment the MaxAuthTries failure counter.
|
||||||
|
const maxAuthServerAttempts = 128
|
||||||
|
|
||||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||||
if config.PreAuthConnCallback != nil {
|
if config.PreAuthConnCallback != nil {
|
||||||
config.PreAuthConnCallback(s)
|
config.PreAuthConnCallback(s)
|
||||||
@@ -537,6 +632,7 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
|
|||||||
var perms *Permissions
|
var perms *Permissions
|
||||||
|
|
||||||
authFailures := 0
|
authFailures := 0
|
||||||
|
authAttempts := 0
|
||||||
noneAuthCount := 0
|
noneAuthCount := 0
|
||||||
var authErrs []error
|
var authErrs []error
|
||||||
var calledBannerCallback bool
|
var calledBannerCallback bool
|
||||||
@@ -565,6 +661,19 @@ userAuthLoop:
|
|||||||
return nil, &ServerAuthError{Errors: authErrs}
|
return nil, &ServerAuthError{Errors: authErrs}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if authAttempts >= maxAuthServerAttempts {
|
||||||
|
discMsg := &disconnectMsg{
|
||||||
|
Reason: 2,
|
||||||
|
Message: "too many authentication attempts",
|
||||||
|
}
|
||||||
|
if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authErrs = append(authErrs, discMsg)
|
||||||
|
return nil, &ServerAuthError{Errors: authErrs}
|
||||||
|
}
|
||||||
|
authAttempts++
|
||||||
|
|
||||||
var userAuthReq userAuthRequestMsg
|
var userAuthReq userAuthRequestMsg
|
||||||
if packet, err := s.transport.readPacket(); err != nil {
|
if packet, err := s.transport.readPacket(); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -737,8 +846,15 @@ userAuthLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
|
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData)
|
||||||
|
// pubKey is reused below for VerifiedPublicKeyCallback and
|
||||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
// must remain the key as presented by the client; derive a
|
||||||
|
// separate value for Verify that carries any applicable
|
||||||
|
// no-touch-required opt-out.
|
||||||
|
pubKeyForVerify := pubKey
|
||||||
|
if noTouchAllowed(pubKey, candidate.perms) {
|
||||||
|
pubKeyForVerify = skKeyWithoutUP(pubKey)
|
||||||
|
}
|
||||||
|
if err := pubKeyForVerify.Verify(signedData, sig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,9 +866,16 @@ userAuthLoop:
|
|||||||
// considered verified and the callback must not run.
|
// considered verified and the callback must not run.
|
||||||
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
||||||
}
|
}
|
||||||
|
if authErr == nil && perms != nil && perms.CriticalOptions != nil {
|
||||||
|
if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" {
|
||||||
|
if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil {
|
||||||
|
authErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "gssapi-with-mic":
|
case "gssapi-with-mic":
|
||||||
if authConfig.GSSAPIWithMICConfig == nil {
|
if !gssapiWithMICConfigured(authConfig.GSSAPIWithMICConfig) {
|
||||||
authErr = errors.New("ssh: gssapi-with-mic auth not configured")
|
authErr = errors.New("ssh: gssapi-with-mic auth not configured")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -824,6 +947,13 @@ userAuthLoop:
|
|||||||
var failureMsg userAuthFailureMsg
|
var failureMsg userAuthFailureMsg
|
||||||
|
|
||||||
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
if partialSuccess, ok := authErr.(*PartialSuccessError); ok {
|
||||||
|
// Permissions are not preserved between authentication steps. To
|
||||||
|
// avoid confusion about the final state of the connection, we
|
||||||
|
// disallow returning non-nil Permissions combined with
|
||||||
|
// PartialSuccessError.
|
||||||
|
if perms != nil {
|
||||||
|
return nil, errors.New("ssh: permissions must be nil when returning PartialSuccessError")
|
||||||
|
}
|
||||||
// After a partial success error we don't allow changing the user
|
// After a partial success error we don't allow changing the user
|
||||||
// name and execute the NoClientAuthCallback.
|
// name and execute the NoClientAuthCallback.
|
||||||
partialSuccessReturned = true
|
partialSuccessReturned = true
|
||||||
@@ -878,8 +1008,7 @@ userAuthLoop:
|
|||||||
if authConfig.KeyboardInteractiveCallback != nil {
|
if authConfig.KeyboardInteractiveCallback != nil {
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
||||||
}
|
}
|
||||||
if authConfig.GSSAPIWithMICConfig != nil && authConfig.GSSAPIWithMICConfig.Server != nil &&
|
if gssapiWithMICConfigured(authConfig.GSSAPIWithMICConfig) {
|
||||||
authConfig.GSSAPIWithMICConfig.AllowLogin != nil {
|
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic")
|
failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -423,6 +423,9 @@ func (s *Session) wait(reqs <-chan *Request) error {
|
|||||||
for msg := range reqs {
|
for msg := range reqs {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "exit-status":
|
case "exit-status":
|
||||||
|
if len(msg.Payload) < 4 {
|
||||||
|
return errors.New("ssh: malformed exit-status request")
|
||||||
|
}
|
||||||
wm.status = int(binary.BigEndian.Uint32(msg.Payload))
|
wm.status = int(binary.BigEndian.Uint32(msg.Payload))
|
||||||
case "exit-signal":
|
case "exit-signal":
|
||||||
var sigval struct {
|
var sigval struct {
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
This package (golang.org/x/net/http2) is the original source of truth
|
||||||
|
of the Go HTTP/2 implementation.
|
||||||
|
|
||||||
|
As of Go 1.27, the source of truth has moved to the standard library
|
||||||
|
package net/http/internal/http2.
|
||||||
|
All new feature development should happen in that package.
|
||||||
|
Only critical bug fixes and security fixes will be backported to x/net.
|
||||||
|
|
||||||
|
The x/net package contains two implementations of the HTTP/2 transport and server:
|
||||||
|
|
||||||
|
The original implementation (no longer the source of truth).
|
||||||
|
|
||||||
|
A reimplementation of the x/net/http2 APIs in terms of net/http.
|
||||||
|
This is called "the wrapping implementation", since it wraps net/http.
|
||||||
|
|
||||||
|
The original implementation is used when the Go version is less than 1.27.
|
||||||
|
|
||||||
|
The wrapping implementation is used when the Go version is at least 1.27.
|
||||||
|
The build tag "http2legacy" may be set to use the original implementation.
|
||||||
+2
-12
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
// Transport code's client connection pooling.
|
// Transport code's client connection pooling.
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
@@ -14,18 +16,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
|
||||||
type ClientConnPool interface {
|
|
||||||
// GetClientConn returns a specific HTTP/2 connection (usually
|
|
||||||
// a TLS-TCP connection) to an HTTP/2 server. On success, the
|
|
||||||
// returned ClientConn accounts for the upcoming RoundTrip
|
|
||||||
// call, so the caller should not omit it. If the caller needs
|
|
||||||
// to, ClientConn.RoundTrip can be called with a bogus
|
|
||||||
// new(http.Request) to release the stream reservation.
|
|
||||||
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
|
||||||
MarkDead(*ClientConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||||
// implementations which can close their idle connections.
|
// implementations which can close their idle connections.
|
||||||
type clientConnPoolIdleCloser interface {
|
type clientConnPoolIdleCloser interface {
|
||||||
|
|||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return cc.roundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDoNotReuse marks cc as not reusable for future HTTP requests.
|
||||||
|
func (cc *ClientConn) SetDoNotReuse() {
|
||||||
|
cc.setDoNotReuse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanTakeNewRequest reports whether the connection can take a new request,
|
||||||
|
// meaning it has not been closed or received or sent a GOAWAY.
|
||||||
|
//
|
||||||
|
// If the caller is going to immediately make a new request on this
|
||||||
|
// connection, use ReserveNewRequest instead.
|
||||||
|
func (cc *ClientConn) CanTakeNewRequest() bool {
|
||||||
|
return cc.canTakeNewRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReserveNewRequest is like CanTakeNewRequest but also reserves a
|
||||||
|
// concurrent stream in cc. The reservation is decremented on the
|
||||||
|
// next call to RoundTrip.
|
||||||
|
func (cc *ClientConn) ReserveNewRequest() bool {
|
||||||
|
return cc.reserveNewRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
// State returns a snapshot of cc's state.
|
||||||
|
func (cc *ClientConn) State() ClientConnState {
|
||||||
|
return cc.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully closes the client connection, waiting for running streams to complete.
|
||||||
|
func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
||||||
|
return cc.shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the client connection immediately.
|
||||||
|
//
|
||||||
|
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
||||||
|
func (cc *ClientConn) Close() error {
|
||||||
|
return cc.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping sends a PING frame to the server and waits for the ack.
|
||||||
|
func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||||
|
return cc.ping(ctx)
|
||||||
|
}
|
||||||
+2
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+3
-10
@@ -6,7 +6,6 @@ package hpack
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// headerFieldTable implements a list of HeaderFields.
|
// headerFieldTable implements a list of HeaderFields.
|
||||||
@@ -55,16 +54,10 @@ func (t *headerFieldTable) len() int {
|
|||||||
|
|
||||||
// addEntry adds a new entry.
|
// addEntry adds a new entry.
|
||||||
func (t *headerFieldTable) addEntry(f HeaderField) {
|
func (t *headerFieldTable) addEntry(f HeaderField) {
|
||||||
// Prevent f from escaping to the heap.
|
|
||||||
f2 := HeaderField{
|
|
||||||
Name: strings.Clone(f.Name),
|
|
||||||
Value: strings.Clone(f.Value),
|
|
||||||
Sensitive: f.Sensitive,
|
|
||||||
}
|
|
||||||
id := uint64(t.len()) + t.evictCount + 1
|
id := uint64(t.len()) + t.evictCount + 1
|
||||||
t.byName[f2.Name] = id
|
t.byName[f.Name] = id
|
||||||
t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
|
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
||||||
t.ents = append(t.ents, f2)
|
t.ents = append(t.ents, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evictOldest evicts the n oldest entries in the table.
|
// evictOldest evicts the n oldest entries in the table.
|
||||||
|
|||||||
+1
-1
@@ -195,7 +195,7 @@ func (s SettingID) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validWireHeaderFieldName reports whether v is a valid header field
|
// validWireHeaderFieldName reports whether v is a valid header field
|
||||||
// name (key). See httpguts.ValidHeaderName for the base rules.
|
// name (key). See httpguts.ValidHeaderFieldName for the base rules.
|
||||||
//
|
//
|
||||||
// Further, http2 says:
|
// Further, http2 says:
|
||||||
//
|
//
|
||||||
|
|||||||
+9
-205
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
// TODO: turn off the serve goroutine when idle, so
|
// TODO: turn off the serve goroutine when idle, so
|
||||||
// an idle conn only has the readFrames goroutine active. (which could
|
// an idle conn only has the readFrames goroutine active. (which could
|
||||||
// also be optimized probably to pin less memory in crypto/tls). This
|
// also be optimized probably to pin less memory in crypto/tls). This
|
||||||
@@ -88,98 +90,6 @@ var (
|
|||||||
testHookOnPanic func(sc *serverConn, panicVal interface{}) (rePanic bool)
|
testHookOnPanic func(sc *serverConn, panicVal interface{}) (rePanic bool)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is an HTTP/2 server.
|
|
||||||
type Server struct {
|
|
||||||
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
|
|
||||||
// which may run at a time over all connections.
|
|
||||||
// Negative or zero no limit.
|
|
||||||
// TODO: implement
|
|
||||||
MaxHandlers int
|
|
||||||
|
|
||||||
// MaxConcurrentStreams optionally specifies the number of
|
|
||||||
// concurrent streams that each client may have open at a
|
|
||||||
// time. This is unrelated to the number of http.Handler goroutines
|
|
||||||
// which may be active globally, which is MaxHandlers.
|
|
||||||
// If zero, MaxConcurrentStreams defaults to at least 100, per
|
|
||||||
// the HTTP/2 spec's recommendations.
|
|
||||||
MaxConcurrentStreams uint32
|
|
||||||
|
|
||||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
|
||||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
|
||||||
// informs the remote endpoint of the maximum size of the header compression
|
|
||||||
// table used to decode header blocks, in octets. If zero, the default value
|
|
||||||
// of 4096 is used.
|
|
||||||
MaxDecoderHeaderTableSize uint32
|
|
||||||
|
|
||||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
|
||||||
// header compression table used for encoding request headers. Received
|
|
||||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
|
||||||
// the default value of 4096 is used.
|
|
||||||
MaxEncoderHeaderTableSize uint32
|
|
||||||
|
|
||||||
// MaxReadFrameSize optionally specifies the largest frame
|
|
||||||
// this server is willing to read. A valid value is between
|
|
||||||
// 16k and 16M, inclusive. If zero or otherwise invalid, a
|
|
||||||
// default value is used.
|
|
||||||
MaxReadFrameSize uint32
|
|
||||||
|
|
||||||
// PermitProhibitedCipherSuites, if true, permits the use of
|
|
||||||
// cipher suites prohibited by the HTTP/2 spec.
|
|
||||||
PermitProhibitedCipherSuites bool
|
|
||||||
|
|
||||||
// IdleTimeout specifies how long until idle clients should be
|
|
||||||
// closed with a GOAWAY frame. PING frames are not considered
|
|
||||||
// activity for the purposes of IdleTimeout.
|
|
||||||
// If zero or negative, there is no timeout.
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
|
|
||||||
// ReadIdleTimeout is the timeout after which a health check using a ping
|
|
||||||
// frame will be carried out if no frame is received on the connection.
|
|
||||||
// If zero, no health check is performed.
|
|
||||||
ReadIdleTimeout time.Duration
|
|
||||||
|
|
||||||
// PingTimeout is the timeout after which the connection will be closed
|
|
||||||
// if a response to a ping is not received.
|
|
||||||
// If zero, a default of 15 seconds is used.
|
|
||||||
PingTimeout time.Duration
|
|
||||||
|
|
||||||
// WriteByteTimeout is the timeout after which a connection will be
|
|
||||||
// closed if no data can be written to it. The timeout begins when data is
|
|
||||||
// available to write, and is extended whenever any bytes are written.
|
|
||||||
// If zero or negative, there is no timeout.
|
|
||||||
WriteByteTimeout time.Duration
|
|
||||||
|
|
||||||
// MaxUploadBufferPerConnection is the size of the initial flow
|
|
||||||
// control window for each connections. The HTTP/2 spec does not
|
|
||||||
// allow this to be smaller than 65535 or larger than 2^32-1.
|
|
||||||
// If the value is outside this range, a default value will be
|
|
||||||
// used instead.
|
|
||||||
MaxUploadBufferPerConnection int32
|
|
||||||
|
|
||||||
// MaxUploadBufferPerStream is the size of the initial flow control
|
|
||||||
// window for each stream. The HTTP/2 spec does not allow this to
|
|
||||||
// be larger than 2^32-1. If the value is zero or larger than the
|
|
||||||
// maximum, a default value will be used instead.
|
|
||||||
MaxUploadBufferPerStream int32
|
|
||||||
|
|
||||||
// NewWriteScheduler constructs a write scheduler for a connection.
|
|
||||||
// If nil, a default scheduler is chosen.
|
|
||||||
//
|
|
||||||
// Deprecated: User-provided write schedulers are deprecated.
|
|
||||||
NewWriteScheduler func() WriteScheduler
|
|
||||||
|
|
||||||
// CountError, if non-nil, is called on HTTP/2 server errors.
|
|
||||||
// It's intended to increment a metric for monitoring, such
|
|
||||||
// as an expvar or Prometheus metric.
|
|
||||||
// The errType consists of only ASCII word characters.
|
|
||||||
CountError func(errType string)
|
|
||||||
|
|
||||||
// Internal state. This is a pointer (rather than embedded directly)
|
|
||||||
// so that we don't embed a Mutex in this struct, which will make the
|
|
||||||
// struct non-copyable, which might break some callers.
|
|
||||||
state *serverInternalState
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverInternalState struct {
|
type serverInternalState struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
activeConns map[*serverConn]struct{}
|
activeConns map[*serverConn]struct{}
|
||||||
@@ -187,6 +97,9 @@ type serverInternalState struct {
|
|||||||
// Pool of error channels. This is per-Server rather than global
|
// Pool of error channels. This is per-Server rather than global
|
||||||
// because channels can't be reused across synctest bubbles.
|
// because channels can't be reused across synctest bubbles.
|
||||||
errChanPool sync.Pool
|
errChanPool sync.Pool
|
||||||
|
|
||||||
|
// Used in tests.
|
||||||
|
testNewConn func(*serverConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverInternalState) registerConn(sc *serverConn) {
|
func (s *serverInternalState) registerConn(sc *serverConn) {
|
||||||
@@ -239,12 +152,7 @@ func (s *serverInternalState) putErrChan(ch chan error) {
|
|||||||
s.errChanPool.Put(ch)
|
s.errChanPool.Put(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureServer adds HTTP/2 support to a net/http Server.
|
func configureServer(s *http.Server, conf *Server) error {
|
||||||
//
|
|
||||||
// The configuration conf may be nil.
|
|
||||||
//
|
|
||||||
// ConfigureServer must be called before s begins serving.
|
|
||||||
func ConfigureServer(s *http.Server, conf *Server) error {
|
|
||||||
if s == nil {
|
if s == nil {
|
||||||
panic("nil *http.Server")
|
panic("nil *http.Server")
|
||||||
}
|
}
|
||||||
@@ -349,83 +257,6 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeConnOpts are options for the Server.ServeConn method.
|
|
||||||
type ServeConnOpts struct {
|
|
||||||
// Context is the base context to use.
|
|
||||||
// If nil, context.Background is used.
|
|
||||||
Context context.Context
|
|
||||||
|
|
||||||
// BaseConfig optionally sets the base configuration
|
|
||||||
// for values. If nil, defaults are used.
|
|
||||||
BaseConfig *http.Server
|
|
||||||
|
|
||||||
// Handler specifies which handler to use for processing
|
|
||||||
// requests. If nil, BaseConfig.Handler is used. If BaseConfig
|
|
||||||
// or BaseConfig.Handler is nil, http.DefaultServeMux is used.
|
|
||||||
Handler http.Handler
|
|
||||||
|
|
||||||
// UpgradeRequest is an initial request received on a connection
|
|
||||||
// undergoing an h2c upgrade. The request body must have been
|
|
||||||
// completely read from the connection before calling ServeConn,
|
|
||||||
// and the 101 Switching Protocols response written.
|
|
||||||
UpgradeRequest *http.Request
|
|
||||||
|
|
||||||
// Settings is the decoded contents of the HTTP2-Settings header
|
|
||||||
// in an h2c upgrade request.
|
|
||||||
Settings []byte
|
|
||||||
|
|
||||||
// SawClientPreface is set if the HTTP/2 connection preface
|
|
||||||
// has already been read from the connection.
|
|
||||||
SawClientPreface bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ServeConnOpts) context() context.Context {
|
|
||||||
if o != nil && o.Context != nil {
|
|
||||||
return o.Context
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ServeConnOpts) baseConfig() *http.Server {
|
|
||||||
if o != nil && o.BaseConfig != nil {
|
|
||||||
return o.BaseConfig
|
|
||||||
}
|
|
||||||
return new(http.Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ServeConnOpts) handler() http.Handler {
|
|
||||||
if o != nil {
|
|
||||||
if o.Handler != nil {
|
|
||||||
return o.Handler
|
|
||||||
}
|
|
||||||
if o.BaseConfig != nil && o.BaseConfig.Handler != nil {
|
|
||||||
return o.BaseConfig.Handler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.DefaultServeMux
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeConn serves HTTP/2 requests on the provided connection and
|
|
||||||
// blocks until the connection is no longer readable.
|
|
||||||
//
|
|
||||||
// ServeConn starts speaking HTTP/2 assuming that c has not had any
|
|
||||||
// reads or writes. It writes its initial settings frame and expects
|
|
||||||
// to be able to read the preface and settings frame from the
|
|
||||||
// client. If c has a ConnectionState method like a *tls.Conn, the
|
|
||||||
// ConnectionState is used to verify the TLS ciphersuite and to set
|
|
||||||
// the Request.TLS field in Handlers.
|
|
||||||
//
|
|
||||||
// ServeConn does not support h2c by itself. Any h2c support must be
|
|
||||||
// implemented in terms of providing a suitably-behaving net.Conn.
|
|
||||||
//
|
|
||||||
// The opts parameter is optional. If nil, default values are used.
|
|
||||||
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
|
||||||
if opts == nil {
|
|
||||||
opts = &ServeConnOpts{}
|
|
||||||
}
|
|
||||||
s.serveConn(c, opts, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
|
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
|
||||||
baseCtx, cancel := serverConnBaseContext(c, opts)
|
baseCtx, cancel := serverConnBaseContext(c, opts)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -461,6 +292,9 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
|||||||
if newf != nil {
|
if newf != nil {
|
||||||
newf(sc)
|
newf(sc)
|
||||||
}
|
}
|
||||||
|
if s.state != nil && s.state.testNewConn != nil {
|
||||||
|
s.state.testNewConn(sc)
|
||||||
|
}
|
||||||
|
|
||||||
s.state.registerConn(sc)
|
s.state.registerConn(sc)
|
||||||
defer s.state.unregisterConn(sc)
|
defer s.state.unregisterConn(sc)
|
||||||
@@ -570,15 +404,6 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
|||||||
sc.serve(conf)
|
sc.serve(conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) {
|
|
||||||
ctx, cancel = context.WithCancel(opts.context())
|
|
||||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
|
||||||
if hs := opts.baseConfig(); hs != nil {
|
|
||||||
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *serverConn) rejectConn(err ErrCode, debug string) {
|
func (sc *serverConn) rejectConn(err ErrCode, debug string) {
|
||||||
sc.vlogf("http2: server rejecting conn: %v, %s", err, debug)
|
sc.vlogf("http2: server rejecting conn: %v, %s", err, debug)
|
||||||
// ignoring errors. hanging up anyway.
|
// ignoring errors. hanging up anyway.
|
||||||
@@ -2832,21 +2657,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
|
|
||||||
// that, if present, signals that the map entry is actually for
|
|
||||||
// the response trailers, and not the response headers. The prefix
|
|
||||||
// is stripped after the ServeHTTP call finishes and the values are
|
|
||||||
// sent in the trailers.
|
|
||||||
//
|
|
||||||
// This mechanism is intended only for trailers that are not known
|
|
||||||
// prior to the headers being written. If the set of trailers is fixed
|
|
||||||
// or known before the header is written, the normal Go trailers mechanism
|
|
||||||
// is preferred:
|
|
||||||
//
|
|
||||||
// https://golang.org/pkg/net/http/#ResponseWriter
|
|
||||||
// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
|
||||||
const TrailerPrefix = "Trailer:"
|
|
||||||
|
|
||||||
// promoteUndeclaredTrailers permits http.Handlers to set trailers
|
// promoteUndeclaredTrailers permits http.Handlers to set trailers
|
||||||
// after the header has already been flushed. Because the Go
|
// after the header has already been flushed. Because the Go
|
||||||
// ResponseWriter interface has no way to set Trailers (only the
|
// ResponseWriter interface has no way to set Trailers (only the
|
||||||
@@ -3123,12 +2933,6 @@ func (w *responseWriter) handlerDone() {
|
|||||||
responseWriterStatePool.Put(rws)
|
responseWriterStatePool.Put(rws)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push errors.
|
|
||||||
var (
|
|
||||||
ErrRecursivePush = errors.New("http2: recursive push not allowed")
|
|
||||||
ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS")
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ http.Pusher = (*responseWriter)(nil)
|
var _ http.Pusher = (*responseWriter)(nil)
|
||||||
|
|
||||||
func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
||||||
|
|||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
|
||||||
|
// that, if present, signals that the map entry is actually for
|
||||||
|
// the response trailers, and not the response headers. The prefix
|
||||||
|
// is stripped after the ServeHTTP call finishes and the values are
|
||||||
|
// sent in the trailers.
|
||||||
|
//
|
||||||
|
// This mechanism is intended only for trailers that are not known
|
||||||
|
// prior to the headers being written. If the set of trailers is fixed
|
||||||
|
// or known before the header is written, the normal Go trailers mechanism
|
||||||
|
// is preferred:
|
||||||
|
//
|
||||||
|
// https://golang.org/pkg/net/http/#ResponseWriter
|
||||||
|
// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||||
|
const TrailerPrefix = "Trailer:"
|
||||||
|
|
||||||
|
// Push errors.
|
||||||
|
var (
|
||||||
|
ErrRecursivePush = errors.New("http2: recursive push not allowed")
|
||||||
|
ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigureServer adds HTTP/2 support to a net/http Server.
|
||||||
|
//
|
||||||
|
// The configuration conf may be nil.
|
||||||
|
//
|
||||||
|
// ConfigureServer must be called before s begins serving.
|
||||||
|
func ConfigureServer(s *http.Server, conf *Server) error {
|
||||||
|
return configureServer(s, conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is an HTTP/2 server.
|
||||||
|
type Server struct {
|
||||||
|
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
|
||||||
|
// which may run at a time over all connections.
|
||||||
|
// Negative or zero no limit.
|
||||||
|
// TODO: implement
|
||||||
|
MaxHandlers int
|
||||||
|
|
||||||
|
// MaxConcurrentStreams optionally specifies the number of
|
||||||
|
// concurrent streams that each client may have open at a
|
||||||
|
// time. This is unrelated to the number of http.Handler goroutines
|
||||||
|
// which may be active globally, which is MaxHandlers.
|
||||||
|
// If zero, MaxConcurrentStreams defaults to at least 100, per
|
||||||
|
// the HTTP/2 spec's recommendations.
|
||||||
|
MaxConcurrentStreams uint32
|
||||||
|
|
||||||
|
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||||
|
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||||
|
// informs the remote endpoint of the maximum size of the header compression
|
||||||
|
// table used to decode header blocks, in octets. If zero, the default value
|
||||||
|
// of 4096 is used.
|
||||||
|
MaxDecoderHeaderTableSize uint32
|
||||||
|
|
||||||
|
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||||
|
// header compression table used for encoding request headers. Received
|
||||||
|
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||||
|
// the default value of 4096 is used.
|
||||||
|
MaxEncoderHeaderTableSize uint32
|
||||||
|
|
||||||
|
// MaxReadFrameSize optionally specifies the largest frame
|
||||||
|
// this server is willing to read. A valid value is between
|
||||||
|
// 16k and 16M, inclusive. If zero or otherwise invalid, a
|
||||||
|
// default value is used.
|
||||||
|
MaxReadFrameSize uint32
|
||||||
|
|
||||||
|
// PermitProhibitedCipherSuites, if true, permits the use of
|
||||||
|
// cipher suites prohibited by the HTTP/2 spec.
|
||||||
|
PermitProhibitedCipherSuites bool
|
||||||
|
|
||||||
|
// IdleTimeout specifies how long until idle clients should be
|
||||||
|
// closed with a GOAWAY frame. PING frames are not considered
|
||||||
|
// activity for the purposes of IdleTimeout.
|
||||||
|
// If zero or negative, there is no timeout.
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadIdleTimeout is the timeout after which a health check using a ping
|
||||||
|
// frame will be carried out if no frame is received on the connection.
|
||||||
|
// If zero, no health check is performed.
|
||||||
|
ReadIdleTimeout time.Duration
|
||||||
|
|
||||||
|
// PingTimeout is the timeout after which the connection will be closed
|
||||||
|
// if a response to a ping is not received.
|
||||||
|
// If zero, a default of 15 seconds is used.
|
||||||
|
PingTimeout time.Duration
|
||||||
|
|
||||||
|
// WriteByteTimeout is the timeout after which a connection will be
|
||||||
|
// closed if no data can be written to it. The timeout begins when data is
|
||||||
|
// available to write, and is extended whenever any bytes are written.
|
||||||
|
// If zero or negative, there is no timeout.
|
||||||
|
WriteByteTimeout time.Duration
|
||||||
|
|
||||||
|
// MaxUploadBufferPerConnection is the size of the initial flow
|
||||||
|
// control window for each connections. The HTTP/2 spec does not
|
||||||
|
// allow this to be smaller than 65535 or larger than 2^32-1.
|
||||||
|
// If the value is outside this range, a default value will be
|
||||||
|
// used instead.
|
||||||
|
MaxUploadBufferPerConnection int32
|
||||||
|
|
||||||
|
// MaxUploadBufferPerStream is the size of the initial flow control
|
||||||
|
// window for each stream. The HTTP/2 spec does not allow this to
|
||||||
|
// be larger than 2^32-1. If the value is zero or larger than the
|
||||||
|
// maximum, a default value will be used instead.
|
||||||
|
MaxUploadBufferPerStream int32
|
||||||
|
|
||||||
|
// NewWriteScheduler constructs a write scheduler for a connection.
|
||||||
|
// If nil, a default scheduler is chosen.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
NewWriteScheduler func() WriteScheduler
|
||||||
|
|
||||||
|
// CountError, if non-nil, is called on HTTP/2 server errors.
|
||||||
|
// It's intended to increment a metric for monitoring, such
|
||||||
|
// as an expvar or Prometheus metric.
|
||||||
|
// The errType consists of only ASCII word characters.
|
||||||
|
CountError func(errType string)
|
||||||
|
|
||||||
|
// Internal state. This is a pointer (rather than embedded directly)
|
||||||
|
// so that we don't embed a Mutex in this struct, which will make the
|
||||||
|
// struct non-copyable, which might break some callers.
|
||||||
|
state *serverInternalState
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeConnOpts are options for the Server.ServeConn method.
|
||||||
|
type ServeConnOpts struct {
|
||||||
|
// Context is the base context to use.
|
||||||
|
// If nil, context.Background is used.
|
||||||
|
Context context.Context
|
||||||
|
|
||||||
|
// BaseConfig optionally sets the base configuration
|
||||||
|
// for values. If nil, defaults are used.
|
||||||
|
BaseConfig *http.Server
|
||||||
|
|
||||||
|
// Handler specifies which handler to use for processing
|
||||||
|
// requests. If nil, BaseConfig.Handler is used. If BaseConfig
|
||||||
|
// or BaseConfig.Handler is nil, http.DefaultServeMux is used.
|
||||||
|
Handler http.Handler
|
||||||
|
|
||||||
|
// UpgradeRequest is an initial request received on a connection
|
||||||
|
// undergoing an h2c upgrade. The request body must have been
|
||||||
|
// completely read from the connection before calling ServeConn,
|
||||||
|
// and the 101 Switching Protocols response written.
|
||||||
|
UpgradeRequest *http.Request
|
||||||
|
|
||||||
|
// Settings is the decoded contents of the HTTP2-Settings header
|
||||||
|
// in an h2c upgrade request.
|
||||||
|
Settings []byte
|
||||||
|
|
||||||
|
// SawClientPreface is set if the HTTP/2 connection preface
|
||||||
|
// has already been read from the connection.
|
||||||
|
SawClientPreface bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeConn serves HTTP/2 requests on the provided connection and
|
||||||
|
// blocks until the connection is no longer readable.
|
||||||
|
//
|
||||||
|
// ServeConn starts speaking HTTP/2 assuming that c has not had any
|
||||||
|
// reads or writes. It writes its initial settings frame and expects
|
||||||
|
// to be able to read the preface and settings frame from the
|
||||||
|
// client. If c has a ConnectionState method like a *tls.Conn, the
|
||||||
|
// ConnectionState is used to verify the TLS ciphersuite and to set
|
||||||
|
// the Request.TLS field in Handlers.
|
||||||
|
//
|
||||||
|
// ServeConn does not support h2c by itself. Any h2c support must be
|
||||||
|
// implemented in terms of providing a suitably-behaving net.Conn.
|
||||||
|
//
|
||||||
|
// The opts parameter is optional. If nil, default values are used.
|
||||||
|
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = &ServeConnOpts{}
|
||||||
|
}
|
||||||
|
s.serveConn(c, opts, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ServeConnOpts) context() context.Context {
|
||||||
|
if o != nil && o.Context != nil {
|
||||||
|
return o.Context
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ServeConnOpts) baseConfig() *http.Server {
|
||||||
|
if o != nil && o.BaseConfig != nil {
|
||||||
|
return o.BaseConfig
|
||||||
|
}
|
||||||
|
return new(http.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ServeConnOpts) handler() http.Handler {
|
||||||
|
if o != nil {
|
||||||
|
if o.Handler != nil {
|
||||||
|
return o.Handler
|
||||||
|
}
|
||||||
|
if o.BaseConfig != nil && o.BaseConfig.Handler != nil {
|
||||||
|
return o.BaseConfig.Handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.DefaultServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) {
|
||||||
|
ctx, cancel = context.WithCancel(opts.context())
|
||||||
|
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
||||||
|
if hs := opts.baseConfig(); hs != nil {
|
||||||
|
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.27 && !http2legacy
|
||||||
|
|
||||||
|
// Server wrapping a net/http.Server.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverInternalState struct {
|
||||||
|
s1 *http.Server
|
||||||
|
initOnce sync.Once
|
||||||
|
serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureServer(s *http.Server, conf *Server) error {
|
||||||
|
if s == nil {
|
||||||
|
panic("nil *http.Server")
|
||||||
|
}
|
||||||
|
if conf == nil {
|
||||||
|
conf = new(Server)
|
||||||
|
}
|
||||||
|
if conf.state != nil {
|
||||||
|
// This isn't a panic in the pre-wrapping implementation,
|
||||||
|
// but calling ConfigureServer twice with the same http2.Server
|
||||||
|
// overwrites internal state on the server.
|
||||||
|
// Make the error explicit and early here.
|
||||||
|
panic("ConfigureServer may be called only once per Server")
|
||||||
|
}
|
||||||
|
if h1, h2 := s, conf; h2.IdleTimeout == 0 {
|
||||||
|
if h1.IdleTimeout != 0 {
|
||||||
|
h2.IdleTimeout = h1.IdleTimeout
|
||||||
|
} else {
|
||||||
|
h2.IdleTimeout = h1.ReadTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register h2 and http/1.1 ALPN protocols on s.TLSConfig, matching
|
||||||
|
// the pre-wrapping implementation in server.go, so that TLS listeners
|
||||||
|
// built from s.TLSConfig still negotiate HTTP/2.
|
||||||
|
if s.TLSConfig == nil {
|
||||||
|
s.TLSConfig = new(tls.Config)
|
||||||
|
}
|
||||||
|
if !slices.Contains(s.TLSConfig.NextProtos, NextProtoTLS) {
|
||||||
|
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, NextProtoTLS)
|
||||||
|
}
|
||||||
|
if !slices.Contains(s.TLSConfig.NextProtos, "http/1.1") {
|
||||||
|
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "http/1.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.state = &serverInternalState{
|
||||||
|
s1: s,
|
||||||
|
}
|
||||||
|
sconfig := &serverConfig{s: conf}
|
||||||
|
if err := s.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||||
|
panic("http2: net/http does not support this version of x/net/http2")
|
||||||
|
}
|
||||||
|
conf.state.serveConnFunc = sconfig.serveConnFunc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
s *Server
|
||||||
|
serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*serverConfig) Accept() (net.Conn, error) {
|
||||||
|
return nil, errors.New("unexpected call to Accept")
|
||||||
|
}
|
||||||
|
func (*serverConfig) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (*serverConfig) Addr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverConfig) ServeConnFunc(f func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)) {
|
||||||
|
s.serveConnFunc = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverConfig) HTTP2Config() http.HTTP2Config {
|
||||||
|
return http.HTTP2Config{
|
||||||
|
MaxConcurrentStreams: int(s.s.MaxConcurrentStreams),
|
||||||
|
MaxDecoderHeaderTableSize: int(s.s.MaxDecoderHeaderTableSize),
|
||||||
|
MaxEncoderHeaderTableSize: int(s.s.MaxEncoderHeaderTableSize),
|
||||||
|
MaxReadFrameSize: int(s.s.MaxReadFrameSize),
|
||||||
|
PermitProhibitedCipherSuites: s.s.PermitProhibitedCipherSuites,
|
||||||
|
MaxReceiveBufferPerConnection: int(s.s.MaxUploadBufferPerConnection),
|
||||||
|
MaxReceiveBufferPerStream: int(s.s.MaxUploadBufferPerStream),
|
||||||
|
SendPingTimeout: s.s.ReadIdleTimeout,
|
||||||
|
PingTimeout: s.s.PingTimeout,
|
||||||
|
WriteByteTimeout: s.s.WriteByteTimeout,
|
||||||
|
CountError: s.s.CountError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverConfig) IdleTimeout() time.Duration {
|
||||||
|
return s.s.IdleTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConn struct{}
|
||||||
|
|
||||||
|
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, _ func(*serverConn)) {
|
||||||
|
var serveConnFunc func(context.Context, net.Conn, http.Handler, bool, *http.Request, []byte)
|
||||||
|
switch {
|
||||||
|
case opts.BaseConfig != nil:
|
||||||
|
// The user has provided us with an http.Server to take configuration from.
|
||||||
|
//
|
||||||
|
// We can't send our request to opts.BaseConfig, because an http.Server can
|
||||||
|
// only be associated with a single http2.Server and the user might
|
||||||
|
// use this one with several http.Servers.
|
||||||
|
//
|
||||||
|
// We can't send our request to s.state.s1, because it doesn't contain
|
||||||
|
// the right configuration.
|
||||||
|
//
|
||||||
|
// So create a one-off copy of opts.BaseConfig and use it.
|
||||||
|
h1 := &http.Server{
|
||||||
|
TLSConfig: opts.BaseConfig.TLSConfig,
|
||||||
|
ReadTimeout: opts.BaseConfig.ReadTimeout,
|
||||||
|
ReadHeaderTimeout: opts.BaseConfig.ReadHeaderTimeout,
|
||||||
|
WriteTimeout: opts.BaseConfig.WriteTimeout,
|
||||||
|
IdleTimeout: opts.BaseConfig.IdleTimeout,
|
||||||
|
MaxHeaderBytes: opts.BaseConfig.MaxHeaderBytes,
|
||||||
|
ConnState: opts.BaseConfig.ConnState,
|
||||||
|
ErrorLog: opts.BaseConfig.ErrorLog,
|
||||||
|
BaseContext: opts.BaseConfig.BaseContext,
|
||||||
|
ConnContext: opts.BaseConfig.ConnContext,
|
||||||
|
HTTP2: opts.BaseConfig.HTTP2,
|
||||||
|
}
|
||||||
|
sconfig := &serverConfig{s: s}
|
||||||
|
if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||||
|
panic("http2: net/http does not support this version of x/net/http2")
|
||||||
|
}
|
||||||
|
serveConnFunc = sconfig.serveConnFunc
|
||||||
|
case s.state != nil:
|
||||||
|
serveConnFunc = s.state.serveConnFunc
|
||||||
|
default:
|
||||||
|
// Strange-but-true: Server has no concurrency-safe way to initialize
|
||||||
|
// its internal state, so historically ServeConn just doesn't use any
|
||||||
|
// persistent state if you don't call ConfigureServer first.
|
||||||
|
//
|
||||||
|
// If ConfigureServer hasn't been called, create a one-off http.Server
|
||||||
|
// for the connection, since we don't have any way to keep one around for reuse.
|
||||||
|
h1 := &http.Server{}
|
||||||
|
sconfig := &serverConfig{s: s}
|
||||||
|
if err := h1.Serve(sconfig); err != nil || sconfig.serveConnFunc == nil {
|
||||||
|
panic("http2: net/http does not support this version of x/net/http2")
|
||||||
|
}
|
||||||
|
serveConnFunc = sconfig.serveConnFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := serverConnBaseContext(c, opts)
|
||||||
|
defer cancel()
|
||||||
|
serveConnFunc(ctx, c, opts.handler(), opts.SawClientPreface, opts.UpgradeRequest, opts.Settings)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameWriteRequest is a request to write a frame.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
type FrameWriteRequest struct {
|
||||||
|
// Ideally we'd define this in writesched_common.go,
|
||||||
|
// to avoid duplicating an exported symbol across two files,
|
||||||
|
// but the changes required to make this work are fairly large.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr FrameWriteRequest) StreamID() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr FrameWriteRequest) DataSize() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteRequest, int) {
|
||||||
|
return FrameWriteRequest{}, FrameWriteRequest{}, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wr FrameWriteRequest) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPriorityWriteScheduler is deprecated.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||||
|
return unsupportedWriteScheduler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandomWriteScheduler is deprecated.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
func NewRandomWriteScheduler() WriteScheduler {
|
||||||
|
return unsupportedWriteScheduler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsupportedWriteScheduler struct{}
|
||||||
|
|
||||||
|
func (unsupportedWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {}
|
||||||
|
func (unsupportedWriteScheduler) CloseStream(streamID uint32) {}
|
||||||
|
func (unsupportedWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {}
|
||||||
|
func (unsupportedWriteScheduler) Push(wr FrameWriteRequest) {}
|
||||||
|
func (unsupportedWriteScheduler) Pop() (wr FrameWriteRequest, ok bool) {
|
||||||
|
return FrameWriteRequest{}, false
|
||||||
|
}
|
||||||
+28
-425
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
// Transport code.
|
// Transport code.
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
@@ -21,20 +23,17 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
mathrand "math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
"golang.org/x/net/http2/hpack"
|
"golang.org/x/net/http2/hpack"
|
||||||
"golang.org/x/net/idna"
|
|
||||||
"golang.org/x/net/internal/httpcommon"
|
"golang.org/x/net/internal/httpcommon"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,123 +59,7 @@ const (
|
|||||||
defaultMaxConcurrentStreams = 1000
|
defaultMaxConcurrentStreams = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transport is an HTTP/2 Transport.
|
type transportInternal struct {
|
||||||
//
|
|
||||||
// A Transport internally caches connections to servers. It is safe
|
|
||||||
// for concurrent use by multiple goroutines.
|
|
||||||
type Transport struct {
|
|
||||||
// DialTLSContext specifies an optional dial function with context for
|
|
||||||
// creating TLS connections for requests.
|
|
||||||
//
|
|
||||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
|
||||||
//
|
|
||||||
// If the returned net.Conn has a ConnectionState method like tls.Conn,
|
|
||||||
// it will be used to set http.Response.TLS.
|
|
||||||
DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
|
|
||||||
|
|
||||||
// DialTLS specifies an optional dial function for creating
|
|
||||||
// TLS connections for requests.
|
|
||||||
//
|
|
||||||
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
|
||||||
//
|
|
||||||
// Deprecated: Use DialTLSContext instead, which allows the transport
|
|
||||||
// to cancel dials as soon as they are no longer needed.
|
|
||||||
// If both are set, DialTLSContext takes priority.
|
|
||||||
DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error)
|
|
||||||
|
|
||||||
// TLSClientConfig specifies the TLS configuration to use with
|
|
||||||
// tls.Client. If nil, the default configuration is used.
|
|
||||||
TLSClientConfig *tls.Config
|
|
||||||
|
|
||||||
// ConnPool optionally specifies an alternate connection pool to use.
|
|
||||||
// If nil, the default is used.
|
|
||||||
ConnPool ClientConnPool
|
|
||||||
|
|
||||||
// DisableCompression, if true, prevents the Transport from
|
|
||||||
// requesting compression with an "Accept-Encoding: gzip"
|
|
||||||
// request header when the Request contains no existing
|
|
||||||
// Accept-Encoding value. If the Transport requests gzip on
|
|
||||||
// its own and gets a gzipped response, it's transparently
|
|
||||||
// decoded in the Response.Body. However, if the user
|
|
||||||
// explicitly requested gzip it is not automatically
|
|
||||||
// uncompressed.
|
|
||||||
DisableCompression bool
|
|
||||||
|
|
||||||
// AllowHTTP, if true, permits HTTP/2 requests using the insecure,
|
|
||||||
// plain-text "http" scheme. Note that this does not enable h2c support.
|
|
||||||
AllowHTTP bool
|
|
||||||
|
|
||||||
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
|
||||||
// send in the initial settings frame. It is how many bytes
|
|
||||||
// of response headers are allowed. Unlike the http2 spec, zero here
|
|
||||||
// means to use a default limit (currently 10MB). If you actually
|
|
||||||
// want to advertise an unlimited value to the peer, Transport
|
|
||||||
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
|
||||||
// to mean no limit.
|
|
||||||
MaxHeaderListSize uint32
|
|
||||||
|
|
||||||
// MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
|
|
||||||
// initial settings frame. It is the size in bytes of the largest frame
|
|
||||||
// payload that the sender is willing to receive. If 0, no setting is
|
|
||||||
// sent, and the value is provided by the peer, which should be 16384
|
|
||||||
// according to the spec:
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
|
|
||||||
// Values are bounded in the range 16k to 16M.
|
|
||||||
MaxReadFrameSize uint32
|
|
||||||
|
|
||||||
// MaxDecoderHeaderTableSize optionally specifies the http2
|
|
||||||
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
|
||||||
// informs the remote endpoint of the maximum size of the header compression
|
|
||||||
// table used to decode header blocks, in octets. If zero, the default value
|
|
||||||
// of 4096 is used.
|
|
||||||
MaxDecoderHeaderTableSize uint32
|
|
||||||
|
|
||||||
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
|
||||||
// header compression table used for encoding request headers. Received
|
|
||||||
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
|
||||||
// the default value of 4096 is used.
|
|
||||||
MaxEncoderHeaderTableSize uint32
|
|
||||||
|
|
||||||
// StrictMaxConcurrentStreams controls whether the server's
|
|
||||||
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
|
|
||||||
// globally. If false, new TCP connections are created to the
|
|
||||||
// server as needed to keep each under the per-connection
|
|
||||||
// SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the
|
|
||||||
// server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as
|
|
||||||
// a global limit and callers of RoundTrip block when needed,
|
|
||||||
// waiting for their turn.
|
|
||||||
StrictMaxConcurrentStreams bool
|
|
||||||
|
|
||||||
// IdleConnTimeout is the maximum amount of time an idle
|
|
||||||
// (keep-alive) connection will remain idle before closing
|
|
||||||
// itself.
|
|
||||||
// Zero means no limit.
|
|
||||||
IdleConnTimeout time.Duration
|
|
||||||
|
|
||||||
// ReadIdleTimeout is the timeout after which a health check using ping
|
|
||||||
// frame will be carried out if no frame is received on the connection.
|
|
||||||
// Note that a ping response will is considered a received frame, so if
|
|
||||||
// there is no other traffic on the connection, the health check will
|
|
||||||
// be performed every ReadIdleTimeout interval.
|
|
||||||
// If zero, no health check is performed.
|
|
||||||
ReadIdleTimeout time.Duration
|
|
||||||
|
|
||||||
// PingTimeout is the timeout after which the connection will be closed
|
|
||||||
// if a response to Ping is not received.
|
|
||||||
// Defaults to 15s.
|
|
||||||
PingTimeout time.Duration
|
|
||||||
|
|
||||||
// WriteByteTimeout is the timeout after which the connection will be
|
|
||||||
// closed no data can be written to it. The timeout begins when data is
|
|
||||||
// available to write, and is extended whenever any bytes are written.
|
|
||||||
WriteByteTimeout time.Duration
|
|
||||||
|
|
||||||
// CountError, if non-nil, is called on HTTP/2 transport errors.
|
|
||||||
// It's intended to increment a metric for monitoring, such
|
|
||||||
// as an expvar or Prometheus metric.
|
|
||||||
// The errType consists of only ASCII word characters.
|
|
||||||
CountError func(errType string)
|
|
||||||
|
|
||||||
// t1, if non-nil, is the standard library Transport using
|
// t1, if non-nil, is the standard library Transport using
|
||||||
// this transport. Its settings are used (but not its
|
// this transport. Its settings are used (but not its
|
||||||
// RoundTrip method, etc).
|
// RoundTrip method, etc).
|
||||||
@@ -217,27 +100,18 @@ func (t *Transport) disableCompression() bool {
|
|||||||
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
func configureTransport(t1 *http.Transport) error {
|
||||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
_, err := configureTransports(t1)
|
||||||
//
|
|
||||||
// Use ConfigureTransports instead to configure the HTTP/2 Transport.
|
|
||||||
func ConfigureTransport(t1 *http.Transport) error {
|
|
||||||
_, err := ConfigureTransports(t1)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2.
|
|
||||||
// It returns a new HTTP/2 Transport for further configuration.
|
|
||||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
|
||||||
func ConfigureTransports(t1 *http.Transport) (*Transport, error) {
|
|
||||||
return configureTransports(t1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureTransports(t1 *http.Transport) (*Transport, error) {
|
func configureTransports(t1 *http.Transport) (*Transport, error) {
|
||||||
connPool := new(clientConnPool)
|
connPool := new(clientConnPool)
|
||||||
t2 := &Transport{
|
t2 := &Transport{
|
||||||
ConnPool: noDialClientConnPool{connPool},
|
ConnPool: noDialClientConnPool{connPool},
|
||||||
t1: t1,
|
transportInternal: transportInternal{
|
||||||
|
t1: t1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
connPool.t = t2
|
connPool.t = t2
|
||||||
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||||
@@ -525,68 +399,7 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// noCachedConnError is the concrete type of ErrNoCachedConn, which
|
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||||
// needs to be detected by net/http regardless of whether it's its
|
|
||||||
// bundled version (in h2_bundle.go with a rewritten type name) or
|
|
||||||
// from a user's x/net/http2. As such, as it has a unique method name
|
|
||||||
// (IsHTTP2NoCachedConnError) that net/http sniffs for via func
|
|
||||||
// isNoCachedConnError.
|
|
||||||
type noCachedConnError struct{}
|
|
||||||
|
|
||||||
func (noCachedConnError) IsHTTP2NoCachedConnError() {}
|
|
||||||
func (noCachedConnError) Error() string { return "http2: no cached connection was available" }
|
|
||||||
|
|
||||||
// isNoCachedConnError reports whether err is of type noCachedConnError
|
|
||||||
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
|
|
||||||
// may coexist in the same running program.
|
|
||||||
func isNoCachedConnError(err error) bool {
|
|
||||||
_, ok := err.(interface{ IsHTTP2NoCachedConnError() })
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNoCachedConn error = noCachedConnError{}
|
|
||||||
|
|
||||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
|
||||||
type RoundTripOpt struct {
|
|
||||||
// OnlyCachedConn controls whether RoundTripOpt may
|
|
||||||
// create a new TCP connection. If set true and
|
|
||||||
// no cached connection is available, RoundTripOpt
|
|
||||||
// will return ErrNoCachedConn.
|
|
||||||
OnlyCachedConn bool
|
|
||||||
|
|
||||||
allowHTTP bool // allow http:// URLs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
return t.RoundTripOpt(req, RoundTripOpt{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
|
|
||||||
// and returns a host:port. The port 443 is added if needed.
|
|
||||||
func authorityAddr(scheme string, authority string) (addr string) {
|
|
||||||
host, port, err := net.SplitHostPort(authority)
|
|
||||||
if err != nil { // authority didn't have a port
|
|
||||||
host = authority
|
|
||||||
port = ""
|
|
||||||
}
|
|
||||||
if port == "" { // authority's port was empty
|
|
||||||
port = "443"
|
|
||||||
if scheme == "http" {
|
|
||||||
port = "80"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if a, err := idna.ToASCII(host); err == nil {
|
|
||||||
host = a
|
|
||||||
}
|
|
||||||
// IPv6 address literal, without a port:
|
|
||||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
|
||||||
return host + ":" + port
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(host, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTripOpt is like RoundTrip, but takes options.
|
|
||||||
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
|
||||||
switch req.URL.Scheme {
|
switch req.URL.Scheme {
|
||||||
case "https":
|
case "https":
|
||||||
// Always okay.
|
// Always okay.
|
||||||
@@ -597,126 +410,15 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
|||||||
default:
|
default:
|
||||||
return nil, errors.New("http2: unsupported scheme")
|
return nil, errors.New("http2: unsupported scheme")
|
||||||
}
|
}
|
||||||
|
return t.roundTripViaPool(req, opt, t.connPool())
|
||||||
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
|
||||||
for retry := 0; ; retry++ {
|
|
||||||
cc, err := t.connPool().GetClientConn(req, addr)
|
|
||||||
if err != nil {
|
|
||||||
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
|
|
||||||
traceGotConn(req, cc, reused)
|
|
||||||
res, err := cc.RoundTrip(req)
|
|
||||||
if err != nil && retry <= 6 {
|
|
||||||
roundTripErr := err
|
|
||||||
if req, err = shouldRetryRequest(req, err); err == nil {
|
|
||||||
// After the first retry, do exponential backoff with 10% jitter.
|
|
||||||
if retry == 0 {
|
|
||||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
backoff := float64(uint(1) << (uint(retry) - 1))
|
|
||||||
backoff += backoff * (0.1 * mathrand.Float64())
|
|
||||||
d := time.Second * time.Duration(backoff)
|
|
||||||
tm := time.NewTimer(d)
|
|
||||||
select {
|
|
||||||
case <-tm.C:
|
|
||||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
|
||||||
continue
|
|
||||||
case <-req.Context().Done():
|
|
||||||
tm.Stop()
|
|
||||||
err = req.Context().Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == errClientConnNotEstablished {
|
|
||||||
// This ClientConn was created recently,
|
|
||||||
// this is the first request to use it,
|
|
||||||
// and the connection is closed and not usable.
|
|
||||||
//
|
|
||||||
// In this state, cc.idleTimer will remove the conn from the pool
|
|
||||||
// when it fires. Stop the timer and remove it here so future requests
|
|
||||||
// won't try to use this connection.
|
|
||||||
//
|
|
||||||
// If the timer has already fired and we're racing it, the redundant
|
|
||||||
// call to MarkDead is harmless.
|
|
||||||
if cc.idleTimer != nil {
|
|
||||||
cc.idleTimer.Stop()
|
|
||||||
}
|
|
||||||
t.connPool().MarkDead(cc)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.vlogf("RoundTrip failure: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseIdleConnections closes any connections which were previously
|
func (t *Transport) closeIdleConnections() {
|
||||||
// connected from previous requests but are now sitting idle.
|
|
||||||
// It does not interrupt any connections currently in use.
|
|
||||||
func (t *Transport) CloseIdleConnections() {
|
|
||||||
if cp, ok := t.connPool().(clientConnPoolIdleCloser); ok {
|
if cp, ok := t.connPool().(clientConnPoolIdleCloser); ok {
|
||||||
cp.closeIdleConnections()
|
cp.closeIdleConnections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
errClientConnClosed = errors.New("http2: client conn is closed")
|
|
||||||
errClientConnUnusable = errors.New("http2: client conn not usable")
|
|
||||||
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
|
|
||||||
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
|
||||||
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
|
|
||||||
)
|
|
||||||
|
|
||||||
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
|
||||||
// response headers. It is always called with a non-nil error.
|
|
||||||
// It returns either a request to retry (either the same request, or a
|
|
||||||
// modified clone), or an error if the request can't be replayed.
|
|
||||||
func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
|
||||||
if !canRetryError(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the Body is nil (or http.NoBody), it's safe to reuse
|
|
||||||
// this request and its Body.
|
|
||||||
if req.Body == nil || req.Body == http.NoBody {
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request body can be reset back to its original
|
|
||||||
// state via the optional req.GetBody, do that.
|
|
||||||
if req.GetBody != nil {
|
|
||||||
body, err := req.GetBody()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newReq := *req
|
|
||||||
newReq.Body = body
|
|
||||||
return &newReq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Request.Body can't reset back to the beginning, but we
|
|
||||||
// don't seem to have started to read from it yet, so reuse
|
|
||||||
// the request directly.
|
|
||||||
if err == errClientConnUnusable {
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func canRetryError(err error) bool {
|
|
||||||
if err == errClientConnUnusable || err == errClientConnGotGoAway {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if se, ok := err.(StreamError); ok {
|
|
||||||
return se.Code == ErrCodeRefusedStream
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||||
host, _, err := net.SplitHostPort(addr)
|
host, _, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -743,27 +445,6 @@ func (t *Transport) newTLSConfig(host string) *tls.Config {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) {
|
|
||||||
if t.DialTLSContext != nil {
|
|
||||||
return t.DialTLSContext(ctx, network, addr, tlsCfg)
|
|
||||||
} else if t.DialTLS != nil {
|
|
||||||
return t.DialTLS(network, addr, tlsCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state := tlsCn.ConnectionState()
|
|
||||||
if p := state.NegotiatedProtocol; p != NextProtoTLS {
|
|
||||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, NextProtoTLS)
|
|
||||||
}
|
|
||||||
if !state.NegotiatedProtocolIsMutual {
|
|
||||||
return nil, errors.New("http2: could not negotiate protocol mutually")
|
|
||||||
}
|
|
||||||
return tlsCn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// disableKeepAlives reports whether connections should be closed as
|
// disableKeepAlives reports whether connections should be closed as
|
||||||
// soon as possible after handling the first request.
|
// soon as possible after handling the first request.
|
||||||
func (t *Transport) disableKeepAlives() bool {
|
func (t *Transport) disableKeepAlives() bool {
|
||||||
@@ -777,7 +458,7 @@ func (t *Transport) expectContinueTimeout() time.Duration {
|
|||||||
return t.t1.ExpectContinueTimeout
|
return t.t1.ExpectContinueTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) {
|
||||||
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,8 +571,7 @@ func (cc *ClientConn) healthCheck() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDoNotReuse marks cc as not reusable for future HTTP requests.
|
func (cc *ClientConn) setDoNotReuse() {
|
||||||
func (cc *ClientConn) SetDoNotReuse() {
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
cc.doNotReuse = true
|
cc.doNotReuse = true
|
||||||
@@ -932,21 +612,13 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanTakeNewRequest reports whether the connection can take a new request,
|
func (cc *ClientConn) canTakeNewRequest() bool {
|
||||||
// meaning it has not been closed or received or sent a GOAWAY.
|
|
||||||
//
|
|
||||||
// If the caller is going to immediately make a new request on this
|
|
||||||
// connection, use ReserveNewRequest instead.
|
|
||||||
func (cc *ClientConn) CanTakeNewRequest() bool {
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
return cc.canTakeNewRequestLocked()
|
return cc.canTakeNewRequestLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReserveNewRequest is like CanTakeNewRequest but also reserves a
|
func (cc *ClientConn) reserveNewRequest() bool {
|
||||||
// concurrent stream in cc. The reservation is decremented on the
|
|
||||||
// next call to RoundTrip.
|
|
||||||
func (cc *ClientConn) ReserveNewRequest() bool {
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
if st := cc.idleStateLocked(); !st.canTakeNewRequest {
|
if st := cc.idleStateLocked(); !st.canTakeNewRequest {
|
||||||
@@ -956,41 +628,7 @@ func (cc *ClientConn) ReserveNewRequest() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientConnState describes the state of a ClientConn.
|
func (cc *ClientConn) state() ClientConnState {
|
||||||
type ClientConnState struct {
|
|
||||||
// Closed is whether the connection is closed.
|
|
||||||
Closed bool
|
|
||||||
|
|
||||||
// Closing is whether the connection is in the process of
|
|
||||||
// closing. It may be closing due to shutdown, being a
|
|
||||||
// single-use connection, being marked as DoNotReuse, or
|
|
||||||
// having received a GOAWAY frame.
|
|
||||||
Closing bool
|
|
||||||
|
|
||||||
// StreamsActive is how many streams are active.
|
|
||||||
StreamsActive int
|
|
||||||
|
|
||||||
// StreamsReserved is how many streams have been reserved via
|
|
||||||
// ClientConn.ReserveNewRequest.
|
|
||||||
StreamsReserved int
|
|
||||||
|
|
||||||
// StreamsPending is how many requests have been sent in excess
|
|
||||||
// of the peer's advertised MaxConcurrentStreams setting and
|
|
||||||
// are waiting for other streams to complete.
|
|
||||||
StreamsPending int
|
|
||||||
|
|
||||||
// MaxConcurrentStreams is how many concurrent streams the
|
|
||||||
// peer advertised as acceptable. Zero means no SETTINGS
|
|
||||||
// frame has been received yet.
|
|
||||||
MaxConcurrentStreams uint32
|
|
||||||
|
|
||||||
// LastIdle, if non-zero, is when the connection last
|
|
||||||
// transitioned to idle state.
|
|
||||||
LastIdle time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// State returns a snapshot of cc's state.
|
|
||||||
func (cc *ClientConn) State() ClientConnState {
|
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
maxConcurrent := cc.maxConcurrentStreams
|
maxConcurrent := cc.maxConcurrentStreams
|
||||||
if !cc.seenSettings {
|
if !cc.seenSettings {
|
||||||
@@ -1161,6 +799,12 @@ func (cc *ClientConn) closeIfIdle() {
|
|||||||
cc.closeConn()
|
cc.closeConn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) stopIdleTimer() {
|
||||||
|
if cc.idleTimer != nil {
|
||||||
|
cc.idleTimer.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
@@ -1169,8 +813,7 @@ func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
|||||||
|
|
||||||
var shutdownEnterWaitStateHook = func() {}
|
var shutdownEnterWaitStateHook = func() {}
|
||||||
|
|
||||||
// Shutdown gracefully closes the client connection, waiting for running streams to complete.
|
func (cc *ClientConn) shutdown(ctx context.Context) error {
|
||||||
func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|
||||||
if err := cc.sendGoAway(); err != nil {
|
if err := cc.sendGoAway(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1244,10 +887,7 @@ func (cc *ClientConn) closeForError(err error) {
|
|||||||
cc.closeConn()
|
cc.closeConn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the client connection immediately.
|
func (cc *ClientConn) close() error {
|
||||||
//
|
|
||||||
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
|
||||||
func (cc *ClientConn) Close() error {
|
|
||||||
cc.closeForError(errClientConnForceClosed)
|
cc.closeForError(errClientConnForceClosed)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1301,11 +941,11 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (cc *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
|
||||||
return cc.roundTrip(req, nil)
|
return cc.internalRoundTrip(req, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
func (cc *ClientConn) internalRoundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
cs := &clientStream{
|
cs := &clientStream{
|
||||||
cc: cc,
|
cc: cc,
|
||||||
@@ -2125,19 +1765,6 @@ func (cc *ClientConn) readLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoAwayError is returned by the Transport when the server closes the
|
|
||||||
// TCP connection after sending a GOAWAY frame.
|
|
||||||
type GoAwayError struct {
|
|
||||||
LastStreamID uint32
|
|
||||||
ErrCode ErrCode
|
|
||||||
DebugData string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e GoAwayError) Error() string {
|
|
||||||
return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q",
|
|
||||||
e.LastStreamID, e.ErrCode, e.DebugData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEOFOrNetReadError(err error) bool {
|
func isEOFOrNetReadError(err error) bool {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return true
|
return true
|
||||||
@@ -2978,7 +2605,7 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ping sends a PING frame to the server and waits for the ack.
|
// Ping sends a PING frame to the server and waits for the ack.
|
||||||
func (cc *ClientConn) Ping(ctx context.Context) error {
|
func (cc *ClientConn) ping(ctx context.Context) error {
|
||||||
c := make(chan struct{})
|
c := make(chan struct{})
|
||||||
// Generate a random payload
|
// Generate a random payload
|
||||||
var p [8]byte
|
var p [8]byte
|
||||||
@@ -3092,16 +2719,6 @@ func (cc *ClientConn) vlogf(format string, args ...interface{}) {
|
|||||||
cc.t.vlogf(format, args...)
|
cc.t.vlogf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) vlogf(format string, args ...interface{}) {
|
|
||||||
if VerboseLogs {
|
|
||||||
t.logf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) logf(format string, args ...interface{}) {
|
|
||||||
log.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var noBody io.ReadCloser = noBodyReader{}
|
var noBody io.ReadCloser = noBodyReader{}
|
||||||
|
|
||||||
type noBodyReader struct{}
|
type noBodyReader struct{}
|
||||||
@@ -3417,17 +3034,3 @@ func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.M
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
|
|
||||||
// connection.
|
|
||||||
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
|
|
||||||
dialer := &tls.Dialer{
|
|
||||||
Config: cfg,
|
|
||||||
}
|
|
||||||
cn, err := dialer.DialContext(ctx, network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
|
|
||||||
return tlsCn, nil
|
|
||||||
}
|
|
||||||
|
|||||||
+447
@@ -0,0 +1,447 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
mathrand "math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||||
|
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||||
|
//
|
||||||
|
// Use ConfigureTransports instead to configure the HTTP/2 Transport.
|
||||||
|
func ConfigureTransport(t1 *http.Transport) error {
|
||||||
|
return configureTransport(t1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||||
|
// It returns a new HTTP/2 Transport for further configuration.
|
||||||
|
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||||
|
func ConfigureTransports(t1 *http.Transport) (*Transport, error) {
|
||||||
|
return configureTransports(t1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport is an HTTP/2 Transport.
|
||||||
|
//
|
||||||
|
// A Transport internally caches connections to servers. It is safe
|
||||||
|
// for concurrent use by multiple goroutines.
|
||||||
|
type Transport struct {
|
||||||
|
// DialTLSContext specifies an optional dial function with context for
|
||||||
|
// creating TLS connections for requests.
|
||||||
|
//
|
||||||
|
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||||
|
//
|
||||||
|
// If the returned net.Conn has a ConnectionState method like tls.Conn,
|
||||||
|
// it will be used to set http.Response.TLS.
|
||||||
|
DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||||
|
|
||||||
|
// DialTLS specifies an optional dial function for creating
|
||||||
|
// TLS connections for requests.
|
||||||
|
//
|
||||||
|
// If DialTLSContext and DialTLS is nil, tls.Dial is used.
|
||||||
|
//
|
||||||
|
// Deprecated: Use DialTLSContext instead, which allows the transport
|
||||||
|
// to cancel dials as soon as they are no longer needed.
|
||||||
|
// If both are set, DialTLSContext takes priority.
|
||||||
|
DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error)
|
||||||
|
|
||||||
|
// TLSClientConfig specifies the TLS configuration to use with
|
||||||
|
// tls.Client. If nil, the default configuration is used.
|
||||||
|
TLSClientConfig *tls.Config
|
||||||
|
|
||||||
|
// ConnPool optionally specifies an alternate connection pool to use.
|
||||||
|
// If nil, the default is used.
|
||||||
|
ConnPool ClientConnPool
|
||||||
|
|
||||||
|
// DisableCompression, if true, prevents the Transport from
|
||||||
|
// requesting compression with an "Accept-Encoding: gzip"
|
||||||
|
// request header when the Request contains no existing
|
||||||
|
// Accept-Encoding value. If the Transport requests gzip on
|
||||||
|
// its own and gets a gzipped response, it's transparently
|
||||||
|
// decoded in the Response.Body. However, if the user
|
||||||
|
// explicitly requested gzip it is not automatically
|
||||||
|
// uncompressed.
|
||||||
|
DisableCompression bool
|
||||||
|
|
||||||
|
// AllowHTTP, if true, permits HTTP/2 requests using the insecure,
|
||||||
|
// plain-text "http" scheme. Note that this does not enable h2c support.
|
||||||
|
AllowHTTP bool
|
||||||
|
|
||||||
|
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
||||||
|
// send in the initial settings frame. It is how many bytes
|
||||||
|
// of response headers are allowed. Unlike the http2 spec, zero here
|
||||||
|
// means to use a default limit (currently 10MB). If you actually
|
||||||
|
// want to advertise an unlimited value to the peer, Transport
|
||||||
|
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
||||||
|
// to mean no limit.
|
||||||
|
MaxHeaderListSize uint32
|
||||||
|
|
||||||
|
// MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
|
||||||
|
// initial settings frame. It is the size in bytes of the largest frame
|
||||||
|
// payload that the sender is willing to receive. If 0, no setting is
|
||||||
|
// sent, and the value is provided by the peer, which should be 16384
|
||||||
|
// according to the spec:
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
|
||||||
|
// Values are bounded in the range 16k to 16M.
|
||||||
|
MaxReadFrameSize uint32
|
||||||
|
|
||||||
|
// MaxDecoderHeaderTableSize optionally specifies the http2
|
||||||
|
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
||||||
|
// informs the remote endpoint of the maximum size of the header compression
|
||||||
|
// table used to decode header blocks, in octets. If zero, the default value
|
||||||
|
// of 4096 is used.
|
||||||
|
MaxDecoderHeaderTableSize uint32
|
||||||
|
|
||||||
|
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
||||||
|
// header compression table used for encoding request headers. Received
|
||||||
|
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
||||||
|
// the default value of 4096 is used.
|
||||||
|
MaxEncoderHeaderTableSize uint32
|
||||||
|
|
||||||
|
// StrictMaxConcurrentStreams controls whether the server's
|
||||||
|
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
|
||||||
|
// globally. If false, new TCP connections are created to the
|
||||||
|
// server as needed to keep each under the per-connection
|
||||||
|
// SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the
|
||||||
|
// server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as
|
||||||
|
// a global limit and callers of RoundTrip block when needed,
|
||||||
|
// waiting for their turn.
|
||||||
|
StrictMaxConcurrentStreams bool
|
||||||
|
|
||||||
|
// IdleConnTimeout is the maximum amount of time an idle
|
||||||
|
// (keep-alive) connection will remain idle before closing
|
||||||
|
// itself.
|
||||||
|
// Zero means no limit.
|
||||||
|
IdleConnTimeout time.Duration
|
||||||
|
|
||||||
|
// ReadIdleTimeout is the timeout after which a health check using ping
|
||||||
|
// frame will be carried out if no frame is received on the connection.
|
||||||
|
// Note that a ping response will is considered a received frame, so if
|
||||||
|
// there is no other traffic on the connection, the health check will
|
||||||
|
// be performed every ReadIdleTimeout interval.
|
||||||
|
// If zero, no health check is performed.
|
||||||
|
ReadIdleTimeout time.Duration
|
||||||
|
|
||||||
|
// PingTimeout is the timeout after which the connection will be closed
|
||||||
|
// if a response to Ping is not received.
|
||||||
|
// Defaults to 15s.
|
||||||
|
PingTimeout time.Duration
|
||||||
|
|
||||||
|
// WriteByteTimeout is the timeout after which the connection will be
|
||||||
|
// closed no data can be written to it. The timeout begins when data is
|
||||||
|
// available to write, and is extended whenever any bytes are written.
|
||||||
|
WriteByteTimeout time.Duration
|
||||||
|
|
||||||
|
// CountError, if non-nil, is called on HTTP/2 transport errors.
|
||||||
|
// It's intended to increment a metric for monitoring, such
|
||||||
|
// as an expvar or Prometheus metric.
|
||||||
|
// The errType consists of only ASCII word characters.
|
||||||
|
CountError func(errType string)
|
||||||
|
|
||||||
|
// Internal state, differs between wrapped and non-wrapped implementations.
|
||||||
|
transportInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errClientConnClosed = errors.New("http2: client conn is closed")
|
||||||
|
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
|
||||||
|
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
||||||
|
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
|
||||||
|
errClientConnUnusable = errors.New("http2: client conn not usable")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||||
|
type ClientConnPool interface {
|
||||||
|
// GetClientConn returns a specific HTTP/2 connection (usually
|
||||||
|
// a TLS-TCP connection) to an HTTP/2 server. On success, the
|
||||||
|
// returned ClientConn accounts for the upcoming RoundTrip
|
||||||
|
// call, so the caller should not omit it. If the caller needs
|
||||||
|
// to, ClientConn.RoundTrip can be called with a bogus
|
||||||
|
// new(http.Request) to release the stream reservation.
|
||||||
|
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||||
|
MarkDead(*ClientConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConnState describes the state of a ClientConn.
|
||||||
|
type ClientConnState struct {
|
||||||
|
// Closed is whether the connection is closed.
|
||||||
|
Closed bool
|
||||||
|
|
||||||
|
// Closing is whether the connection is in the process of
|
||||||
|
// closing. It may be closing due to shutdown, being a
|
||||||
|
// single-use connection, being marked as DoNotReuse, or
|
||||||
|
// having received a GOAWAY frame.
|
||||||
|
Closing bool
|
||||||
|
|
||||||
|
// StreamsActive is how many streams are active.
|
||||||
|
StreamsActive int
|
||||||
|
|
||||||
|
// StreamsReserved is how many streams have been reserved via
|
||||||
|
// ClientConn.ReserveNewRequest.
|
||||||
|
StreamsReserved int
|
||||||
|
|
||||||
|
// StreamsPending is how many requests have been sent in excess
|
||||||
|
// of the peer's advertised MaxConcurrentStreams setting and
|
||||||
|
// are waiting for other streams to complete.
|
||||||
|
StreamsPending int
|
||||||
|
|
||||||
|
// MaxConcurrentStreams is how many concurrent streams the
|
||||||
|
// peer advertised as acceptable. Zero means no SETTINGS
|
||||||
|
// frame has been received yet.
|
||||||
|
MaxConcurrentStreams uint32
|
||||||
|
|
||||||
|
// LastIdle, if non-zero, is when the connection last
|
||||||
|
// transitioned to idle state.
|
||||||
|
LastIdle time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||||
|
type RoundTripOpt struct {
|
||||||
|
// OnlyCachedConn controls whether RoundTripOpt may
|
||||||
|
// create a new TCP connection. If set true and
|
||||||
|
// no cached connection is available, RoundTripOpt
|
||||||
|
// will return ErrNoCachedConn.
|
||||||
|
|
||||||
|
// OnlyCachedConn was broken in https://go.dev/cl/16699.
|
||||||
|
OnlyCachedConn bool
|
||||||
|
|
||||||
|
allowHTTP bool // allow http:// URLs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return t.RoundTripOpt(req, RoundTripOpt{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTripOpt is like RoundTrip, but takes options.
|
||||||
|
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||||
|
return t.roundTripOpt(req, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseIdleConnections closes any connections which were previously
|
||||||
|
// connected from previous requests but are now sitting idle.
|
||||||
|
// It does not interrupt any connections currently in use.
|
||||||
|
func (t *Transport) CloseIdleConnections() {
|
||||||
|
t.closeIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||||
|
return t.newUserClientConn(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
|
||||||
|
// and returns a host:port. The port 443 is added if needed.
|
||||||
|
func authorityAddr(scheme string, authority string) (addr string) {
|
||||||
|
host, port, err := net.SplitHostPort(authority)
|
||||||
|
if err != nil { // authority didn't have a port
|
||||||
|
host = authority
|
||||||
|
port = ""
|
||||||
|
}
|
||||||
|
if port == "" { // authority's port was empty
|
||||||
|
port = "443"
|
||||||
|
if scheme == "http" {
|
||||||
|
port = "80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a, err := idna.ToASCII(host); err == nil {
|
||||||
|
host = a
|
||||||
|
}
|
||||||
|
// IPv6 address literal, without a port:
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
return host + ":" + port
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) roundTripViaPool(req *http.Request, opt RoundTripOpt, pool ClientConnPool) (*http.Response, error) {
|
||||||
|
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
||||||
|
for retry := 0; ; retry++ {
|
||||||
|
cc, err := pool.GetClientConn(req, addr)
|
||||||
|
if err != nil {
|
||||||
|
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
|
||||||
|
traceGotConn(req, cc, reused)
|
||||||
|
res, err := cc.RoundTrip(req)
|
||||||
|
if err != nil && retry <= 6 {
|
||||||
|
roundTripErr := err
|
||||||
|
if req, err = shouldRetryRequest(req, err); err == nil {
|
||||||
|
// After the first retry, do exponential backoff with 10% jitter.
|
||||||
|
if retry == 0 {
|
||||||
|
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||||
|
backoff += backoff * (0.1 * mathrand.Float64())
|
||||||
|
d := time.Second * time.Duration(backoff)
|
||||||
|
tm := time.NewTimer(d)
|
||||||
|
select {
|
||||||
|
case <-tm.C:
|
||||||
|
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||||
|
continue
|
||||||
|
case <-req.Context().Done():
|
||||||
|
tm.Stop()
|
||||||
|
err = req.Context().Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == errClientConnNotEstablished {
|
||||||
|
// This ClientConn was created recently,
|
||||||
|
// this is the first request to use it,
|
||||||
|
// and the connection is closed and not usable.
|
||||||
|
//
|
||||||
|
// In this state, cc.idleTimer will remove the conn from the pool
|
||||||
|
// when it fires. Stop the timer and remove it here so future requests
|
||||||
|
// won't try to use this connection.
|
||||||
|
//
|
||||||
|
// If the timer has already fired and we're racing it, the redundant
|
||||||
|
// call to MarkDead is harmless.
|
||||||
|
cc.stopIdleTimer()
|
||||||
|
pool.MarkDead(cc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.vlogf("RoundTrip failure: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
||||||
|
// response headers. It is always called with a non-nil error.
|
||||||
|
// It returns either a request to retry (either the same request, or a
|
||||||
|
// modified clone), or an error if the request can't be replayed.
|
||||||
|
func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
||||||
|
if !canRetryError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If the Body is nil (or http.NoBody), it's safe to reuse
|
||||||
|
// this request and its Body.
|
||||||
|
if req.Body == nil || req.Body == http.NoBody {
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request body can be reset back to its original
|
||||||
|
// state via the optional req.GetBody, do that.
|
||||||
|
if req.GetBody != nil {
|
||||||
|
body, err := req.GetBody()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newReq := *req
|
||||||
|
newReq.Body = body
|
||||||
|
return &newReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Request.Body can't reset back to the beginning, but we
|
||||||
|
// don't seem to have started to read from it yet, so reuse
|
||||||
|
// the request directly.
|
||||||
|
if err == errClientConnUnusable {
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func canRetryError(err error) bool {
|
||||||
|
if err == errClientConnUnusable || err == errClientConnGotGoAway {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if se, ok := err.(StreamError); ok {
|
||||||
|
return se.Code == ErrCodeRefusedStream
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) vlogf(format string, args ...interface{}) {
|
||||||
|
if VerboseLogs {
|
||||||
|
t.logf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) logf(format string, args ...interface{}) {
|
||||||
|
log.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) {
|
||||||
|
if t.DialTLSContext != nil {
|
||||||
|
return t.DialTLSContext(ctx, network, addr, tlsCfg)
|
||||||
|
} else if t.DialTLS != nil {
|
||||||
|
return t.DialTLS(network, addr, tlsCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state := tlsCn.ConnectionState()
|
||||||
|
if p := state.NegotiatedProtocol; p != NextProtoTLS {
|
||||||
|
return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, NextProtoTLS)
|
||||||
|
}
|
||||||
|
if !state.NegotiatedProtocolIsMutual {
|
||||||
|
return nil, errors.New("http2: could not negotiate protocol mutually")
|
||||||
|
}
|
||||||
|
return tlsCn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
|
||||||
|
// connection.
|
||||||
|
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
|
||||||
|
dialer := &tls.Dialer{
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
cn, err := dialer.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
|
||||||
|
return tlsCn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoAwayError is returned by the Transport when the server closes the
|
||||||
|
// TCP connection after sending a GOAWAY frame.
|
||||||
|
type GoAwayError struct {
|
||||||
|
LastStreamID uint32
|
||||||
|
ErrCode ErrCode
|
||||||
|
DebugData string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e GoAwayError) Error() string {
|
||||||
|
return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q",
|
||||||
|
e.LastStreamID, e.ErrCode, e.DebugData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// noCachedConnError is the concrete type of ErrNoCachedConn, which
|
||||||
|
// needs to be detected by net/http regardless of whether it's its
|
||||||
|
// bundled version (in h2_bundle.go with a rewritten type name) or
|
||||||
|
// from a user's x/net/http2. As such, as it has a unique method name
|
||||||
|
// (IsHTTP2NoCachedConnError) that net/http sniffs for via func
|
||||||
|
// isNoCachedConnError.
|
||||||
|
type noCachedConnError struct{}
|
||||||
|
|
||||||
|
func (noCachedConnError) IsHTTP2NoCachedConnError() {}
|
||||||
|
func (noCachedConnError) Error() string { return "http2: no cached connection was available" }
|
||||||
|
|
||||||
|
// isNoCachedConnError reports whether err is of type noCachedConnError
|
||||||
|
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
|
||||||
|
// may coexist in the same running program.
|
||||||
|
func isNoCachedConnError(err error) bool {
|
||||||
|
_, ok := err.(interface{ IsHTTP2NoCachedConnError() })
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNoCachedConn error = noCachedConnError{}
|
||||||
+392
@@ -0,0 +1,392 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.27 && !http2legacy
|
||||||
|
|
||||||
|
// Transport wrapping a net/http.Transport.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func configureTransport(t1 *http.Transport) error {
|
||||||
|
_, err := configureTransports(t1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureTransports(t1 *http.Transport) (*Transport, error) {
|
||||||
|
// ConfigureTransport returns an http2.Transport with a configuration
|
||||||
|
// linked to the http.Transport's.
|
||||||
|
tr2 := &Transport{}
|
||||||
|
tr2.configure(t1)
|
||||||
|
// Enable HTTP/2 on the transport, as the pre-wrapping implementation did:
|
||||||
|
// net/http does not auto-enable it for a transport with a custom
|
||||||
|
// TLSClientConfig or dialer.
|
||||||
|
if t1.TLSClientConfig == nil {
|
||||||
|
t1.TLSClientConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
if t1.Protocols == nil {
|
||||||
|
t1.Protocols = new(http.Protocols)
|
||||||
|
t1.Protocols.SetHTTP1(true)
|
||||||
|
}
|
||||||
|
t1.Protocols.SetHTTP2(true)
|
||||||
|
return tr2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// transportConfig is passed to net/http.Transport.RegisterProtocol("http/2", config).
|
||||||
|
// It provides the net/http.Transport with access to the configuration in the
|
||||||
|
// x/net/http2.Transport.
|
||||||
|
type transportConfig struct {
|
||||||
|
t *Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registered is called by net/http.Transport.RegisterProtocol,
|
||||||
|
// to let us know that it understands the registration mechanism we're using.
|
||||||
|
func (t transportConfig) Registered(t1 *http.Transport) {
|
||||||
|
t.t.t1 = t1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t transportConfig) DisableCompression() bool {
|
||||||
|
return t.t.DisableCompression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t transportConfig) MaxHeaderListSize() int64 {
|
||||||
|
return int64(t.t.MaxHeaderListSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t transportConfig) IdleConnTimeout() time.Duration {
|
||||||
|
return t.t.IdleConnTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t transportConfig) HTTP2Config() http.HTTP2Config {
|
||||||
|
return http.HTTP2Config{
|
||||||
|
StrictMaxConcurrentRequests: t.t.StrictMaxConcurrentStreams,
|
||||||
|
MaxDecoderHeaderTableSize: int(t.t.MaxDecoderHeaderTableSize),
|
||||||
|
MaxEncoderHeaderTableSize: int(t.t.MaxEncoderHeaderTableSize),
|
||||||
|
MaxReadFrameSize: int(t.t.MaxReadFrameSize),
|
||||||
|
SendPingTimeout: t.t.ReadIdleTimeout,
|
||||||
|
PingTimeout: t.t.PingTimeout,
|
||||||
|
WriteByteTimeout: t.t.WriteByteTimeout,
|
||||||
|
CountError: t.t.CountError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalRoundTrip reports whether the Transport wants to take control of the RoundTrip call.
|
||||||
|
// If the user hasn't configured a custom connection pool, we leave the RoundTrip up to net/http.
|
||||||
|
func (t transportConfig) ExternalRoundTrip() bool {
|
||||||
|
return t.t.ConnPool != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip is used when the http.Transport is passing control of the full
|
||||||
|
// RoundTrip to us--connection pooling, retries, etc.
|
||||||
|
//
|
||||||
|
// This is only used when the http2.Transport has a user-provided ConnPool.
|
||||||
|
// Any other time, net/http handles everything.
|
||||||
|
func (t transportConfig) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if t.t.ConnPool == nil {
|
||||||
|
return nil, http.ErrSkipAltProtocol
|
||||||
|
}
|
||||||
|
return t.t.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// netConnContextKey passes a net.Conn to http.Transport.NewClientConn.
|
||||||
|
// See http2.Transport.NewClientConn.
|
||||||
|
type netConnContextKey struct{}
|
||||||
|
|
||||||
|
// ConnFromContext lets the http.Transport fetch a net.Conn out of a context
|
||||||
|
// passed to NewClientConn. See http2.Transport.NewClientConn.
|
||||||
|
func (t transportConfig) ConnFromContext(ctx context.Context) net.Conn {
|
||||||
|
nc, _ := ctx.Value(netConnContextKey{}).(net.Conn)
|
||||||
|
return nc
|
||||||
|
}
|
||||||
|
|
||||||
|
// http2TransportContextKey marks a RoundTrip as needing its dial handled by the http2.Transport.
|
||||||
|
// We set this for http2.RoundTrip calls, where the historical behavior is to use the
|
||||||
|
// http2.Transport's dialer.
|
||||||
|
type http2TransportContextKey struct{}
|
||||||
|
|
||||||
|
// DialFromContext dials a new connection using the http2.Transport's DialTLS/DialTLSContext.
|
||||||
|
func (t transportConfig) DialFromContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
if ctx.Value(http2TransportContextKey{}) == nil {
|
||||||
|
// We're being called from a RoundTrip that did not start with an http2.Transport.
|
||||||
|
// Use the http.Transport's dialer.
|
||||||
|
return nil, errors.ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf := t.t.TLSClientConfig
|
||||||
|
if tlsConf == nil {
|
||||||
|
tlsConf = &tls.Config{}
|
||||||
|
} else {
|
||||||
|
tlsConf = tlsConf.Clone()
|
||||||
|
}
|
||||||
|
if !slices.Contains(tlsConf.NextProtos, "h2") {
|
||||||
|
tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...)
|
||||||
|
}
|
||||||
|
if tlsConf.ServerName == "" {
|
||||||
|
host, _, err := net.SplitHostPort(address)
|
||||||
|
if err == nil {
|
||||||
|
tlsConf.ServerName = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.t.dialTLS(ctx, network, address, tlsConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type transportInternal struct {
|
||||||
|
initOnce sync.Once
|
||||||
|
t1 *http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) init() {
|
||||||
|
t.initOnce.Do(func() {
|
||||||
|
if t.t1 != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t1 := &http.Transport{}
|
||||||
|
t.configure(t1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) configure(t1 *http.Transport) {
|
||||||
|
t1.RegisterProtocol("http/2", transportConfig{t})
|
||||||
|
// tr2.t1 is set by transportConfig.Registered.
|
||||||
|
if t.t1 != t1 {
|
||||||
|
panic("http2: net/http does not support this version of x/net/http2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||||
|
t.init()
|
||||||
|
|
||||||
|
if req.URL.Scheme == "http" && !t.AllowHTTP {
|
||||||
|
return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the Transport has a user-provided connection pool (unusual, deprecated),
|
||||||
|
// we need to handle picking a connection, retrys, etc.
|
||||||
|
if t.ConnPool != nil {
|
||||||
|
return t.roundTripViaPool(req, opt, t.ConnPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting this context key lets net/http know that if it is necessary to dial
|
||||||
|
// a new connection, we should handle the net.Dial.
|
||||||
|
//
|
||||||
|
// Both http.Transport and http2.Transport allow the user to provide a custom
|
||||||
|
// dial function, and historically you only get the dial function from the
|
||||||
|
// Transport you're calling RoundTrip on.
|
||||||
|
ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
return t.t1.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) closeIdleConnections() {
|
||||||
|
t.init()
|
||||||
|
t.t1.CloseIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) newUserClientConn(c net.Conn) (*ClientConn, error) {
|
||||||
|
// http.Transport's NewClientConn doesn't provide a supported way to create
|
||||||
|
// a connection from a net.Conn. (This might be useful to add in the future?)
|
||||||
|
// We're going to craftily sneak one in via the context key, with the
|
||||||
|
// scheme of "http/2" telling NewClientConn to look for it.
|
||||||
|
ctx := context.WithValue(context.Background(), netConnContextKey{}, c)
|
||||||
|
|
||||||
|
nhcc, err := t.t1.NewClientConn(ctx, "http/2", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cc := &ClientConn{cc: nhcc, tr: t, tconn: c}
|
||||||
|
nhcc.SetStateHook(cc.stateHook)
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConn is the state of a single HTTP/2 client connection to an
|
||||||
|
// HTTP/2 server.
|
||||||
|
type ClientConn struct {
|
||||||
|
cc *http.ClientConn
|
||||||
|
tconn net.Conn
|
||||||
|
tr *Transport
|
||||||
|
doNotReuse bool
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
closing bool
|
||||||
|
closed bool
|
||||||
|
roundTrips int
|
||||||
|
reserved int
|
||||||
|
starting int
|
||||||
|
pending int
|
||||||
|
maxConcurrent int
|
||||||
|
lastIdle time.Time
|
||||||
|
shutdownc chan struct{}
|
||||||
|
|
||||||
|
atomicReused uint32 // whether conn is being reused; atomic
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
err := func() error {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
if cc.doNotReuse {
|
||||||
|
return errClientConnUnusable
|
||||||
|
}
|
||||||
|
cc.roundTrips++
|
||||||
|
if cc.reserved > 0 {
|
||||||
|
// We've already reserved a concurrency slot for this request.
|
||||||
|
cc.reserved--
|
||||||
|
} else if cc.cc.Reserve() != nil {
|
||||||
|
// We don't seem to have an available concurrency slot,
|
||||||
|
// so bump the pending count (requests waiting for a slot).
|
||||||
|
cc.pending++
|
||||||
|
}
|
||||||
|
// ClientConn.Shutdown will not shut down the conn while
|
||||||
|
// cc.starting > 0 or cc.cc.InFlight() > 0.
|
||||||
|
//
|
||||||
|
// The starting state covers the gap between us deciding to
|
||||||
|
// start sending the request, and actually sending it.
|
||||||
|
cc.starting++
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := cc.cc.RoundTrip(req)
|
||||||
|
cc.mu.Lock()
|
||||||
|
cc.starting--
|
||||||
|
if cc.pending > 0 {
|
||||||
|
// A request completing frees up a concurrency slot for
|
||||||
|
// a pending request to start.
|
||||||
|
cc.pending--
|
||||||
|
}
|
||||||
|
cc.updateStateLocked()
|
||||||
|
cc.mu.Unlock()
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) canTakeNewRequest() bool {
|
||||||
|
return cc.cc.Available() > 0 && !cc.doNotReuse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) close() error {
|
||||||
|
return cc.cc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) ping(ctx context.Context) error {
|
||||||
|
// Ask net/http to ping its connection by sending a request with a method of ":ping".
|
||||||
|
_, err := cc.cc.RoundTrip((&http.Request{
|
||||||
|
Method: ":ping",
|
||||||
|
}).WithContext(ctx))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) reserveNewRequest() bool {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
if cc.doNotReuse {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := cc.cc.Reserve(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cc.reserved++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) setDoNotReuse() {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
cc.doNotReuse = true
|
||||||
|
cc.closing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) shutdown(ctx context.Context) error {
|
||||||
|
cc.mu.Lock()
|
||||||
|
inFlight := cc.cc.InFlight() + cc.starting
|
||||||
|
if inFlight > 0 && cc.shutdownc == nil {
|
||||||
|
cc.shutdownc = make(chan struct{})
|
||||||
|
}
|
||||||
|
shutdownc := cc.shutdownc
|
||||||
|
cc.mu.Unlock()
|
||||||
|
if shutdownc != nil {
|
||||||
|
// Wait for in-flight requests to finish.
|
||||||
|
select {
|
||||||
|
case <-shutdownc:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc.cc.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) state() ClientConnState {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
cc.updateStateLocked()
|
||||||
|
return ClientConnState{
|
||||||
|
Closed: cc.closed,
|
||||||
|
Closing: cc.closing,
|
||||||
|
StreamsActive: cc.cc.InFlight() - cc.reserved,
|
||||||
|
StreamsReserved: cc.reserved,
|
||||||
|
StreamsPending: cc.pending,
|
||||||
|
MaxConcurrentStreams: uint32(min(int64(cc.maxConcurrent), math.MaxUint32)),
|
||||||
|
LastIdle: cc.lastIdle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateHook is the http.ClientConn's state hook.
|
||||||
|
func (cc *ClientConn) stateHook(*http.ClientConn) {
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
cc.updateStateLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) updateStateLocked() {
|
||||||
|
if cc.cc.Err() != nil && !cc.closed {
|
||||||
|
cc.closing = true
|
||||||
|
cc.closed = true
|
||||||
|
if cc.tr.ConnPool != nil {
|
||||||
|
// Do the ConnPool update in another goroutine,
|
||||||
|
// to avoid holding the conn mutex while it runs.
|
||||||
|
go cc.tr.ConnPool.MarkDead(cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cc.cc.InFlight() == 0 && cc.roundTrips > 0 && cc.starting == 0 {
|
||||||
|
cc.lastIdle = time.Now()
|
||||||
|
}
|
||||||
|
if !cc.closed {
|
||||||
|
// This is slightly racy (a request could start or finish in between
|
||||||
|
// the Available and InFlight calls), but the best we can do given that
|
||||||
|
// the net/http ClientConn API doesn't expose the conn's max concurrency.
|
||||||
|
cc.maxConcurrent = cc.cc.Available() + cc.cc.InFlight()
|
||||||
|
}
|
||||||
|
if cc.shutdownc != nil && cc.cc.InFlight()+cc.starting == 0 {
|
||||||
|
close(cc.shutdownc)
|
||||||
|
cc.shutdownc = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) stopIdleTimer() {}
|
||||||
|
|
||||||
|
// traceGotConn is (when http2legacy is not enabled) only used for tracing
|
||||||
|
// connections acquired while using a user-provided ClientConnPool.
|
||||||
|
func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
|
||||||
|
trace := httptrace.ContextClientTrace(req.Context())
|
||||||
|
if trace == nil || trace.GotConn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ci := httptrace.GotConnInfo{Conn: cc.tconn}
|
||||||
|
ci.Reused = reused
|
||||||
|
trace.GotConn(ci)
|
||||||
|
}
|
||||||
+2
-44
@@ -2,54 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// WriteScheduler is the interface implemented by HTTP/2 write schedulers.
|
|
||||||
// Methods are never called concurrently.
|
|
||||||
//
|
|
||||||
// Deprecated: User-provided write schedulers are deprecated.
|
|
||||||
type WriteScheduler interface {
|
|
||||||
// OpenStream opens a new stream in the write scheduler.
|
|
||||||
// It is illegal to call this with streamID=0 or with a streamID that is
|
|
||||||
// already open -- the call may panic.
|
|
||||||
OpenStream(streamID uint32, options OpenStreamOptions)
|
|
||||||
|
|
||||||
// CloseStream closes a stream in the write scheduler. Any frames queued on
|
|
||||||
// this stream should be discarded. It is illegal to call this on a stream
|
|
||||||
// that is not open -- the call may panic.
|
|
||||||
CloseStream(streamID uint32)
|
|
||||||
|
|
||||||
// AdjustStream adjusts the priority of the given stream. This may be called
|
|
||||||
// on a stream that has not yet been opened or has been closed. Note that
|
|
||||||
// RFC 7540 allows PRIORITY frames to be sent on streams in any state. See:
|
|
||||||
// https://tools.ietf.org/html/rfc7540#section-5.1
|
|
||||||
AdjustStream(streamID uint32, priority PriorityParam)
|
|
||||||
|
|
||||||
// Push queues a frame in the scheduler. In most cases, this will not be
|
|
||||||
// called with wr.StreamID()!=0 unless that stream is currently open. The one
|
|
||||||
// exception is RST_STREAM frames, which may be sent on idle or closed streams.
|
|
||||||
Push(wr FrameWriteRequest)
|
|
||||||
|
|
||||||
// Pop dequeues the next frame to write. Returns false if no frames can
|
|
||||||
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
|
||||||
// order they are Push'd, except RST_STREAM frames. No frames should be
|
|
||||||
// discarded except by CloseStream.
|
|
||||||
Pop() (wr FrameWriteRequest, ok bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream.
|
|
||||||
//
|
|
||||||
// Deprecated: User-provided write schedulers are deprecated.
|
|
||||||
type OpenStreamOptions struct {
|
|
||||||
// PusherID is zero if the stream was initiated by the client. Otherwise,
|
|
||||||
// PusherID names the stream that pushed the newly opened stream.
|
|
||||||
PusherID uint32
|
|
||||||
// priority is used to set the priority of the newly opened stream.
|
|
||||||
priority PriorityParam
|
|
||||||
}
|
|
||||||
|
|
||||||
// FrameWriteRequest is a request to write a frame.
|
// FrameWriteRequest is a request to write a frame.
|
||||||
//
|
//
|
||||||
// Deprecated: User-provided write schedulers are deprecated.
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
|||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
// WriteScheduler is the interface implemented by HTTP/2 write schedulers.
|
||||||
|
// Methods are never called concurrently.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
type WriteScheduler interface {
|
||||||
|
// OpenStream opens a new stream in the write scheduler.
|
||||||
|
// It is illegal to call this with streamID=0 or with a streamID that is
|
||||||
|
// already open -- the call may panic.
|
||||||
|
OpenStream(streamID uint32, options OpenStreamOptions)
|
||||||
|
|
||||||
|
// CloseStream closes a stream in the write scheduler. Any frames queued on
|
||||||
|
// this stream should be discarded. It is illegal to call this on a stream
|
||||||
|
// that is not open -- the call may panic.
|
||||||
|
CloseStream(streamID uint32)
|
||||||
|
|
||||||
|
// AdjustStream adjusts the priority of the given stream. This may be called
|
||||||
|
// on a stream that has not yet been opened or has been closed. Note that
|
||||||
|
// RFC 7540 allows PRIORITY frames to be sent on streams in any state. See:
|
||||||
|
// https://tools.ietf.org/html/rfc7540#section-5.1
|
||||||
|
AdjustStream(streamID uint32, priority PriorityParam)
|
||||||
|
|
||||||
|
// Push queues a frame in the scheduler. In most cases, this will not be
|
||||||
|
// called with wr.StreamID()!=0 unless that stream is currently open. The one
|
||||||
|
// exception is RST_STREAM frames, which may be sent on idle or closed streams.
|
||||||
|
Push(wr FrameWriteRequest)
|
||||||
|
|
||||||
|
// Pop dequeues the next frame to write. Returns false if no frames can
|
||||||
|
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
||||||
|
// order they are Push'd, except RST_STREAM frames. No frames should be
|
||||||
|
// discarded except by CloseStream.
|
||||||
|
Pop() (wr FrameWriteRequest, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
type OpenStreamOptions struct {
|
||||||
|
// PusherID is zero if the stream was initiated by the client. Otherwise,
|
||||||
|
// PusherID names the stream that pushed the newly opened stream.
|
||||||
|
PusherID uint32
|
||||||
|
// priority is used to set the priority of the newly opened stream.
|
||||||
|
priority PriorityParam
|
||||||
|
}
|
||||||
|
|
||||||
|
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
||||||
|
//
|
||||||
|
// Deprecated: User-provided write schedulers are deprecated.
|
||||||
|
type PriorityWriteSchedulerConfig struct {
|
||||||
|
// MaxClosedNodesInTree controls the maximum number of closed streams to
|
||||||
|
// retain in the priority tree. Setting this to zero saves a small amount
|
||||||
|
// of memory at the cost of performance.
|
||||||
|
//
|
||||||
|
// See RFC 7540, Section 5.3.4:
|
||||||
|
// "It is possible for a stream to become closed while prioritization
|
||||||
|
// information ... is in transit. ... This potentially creates suboptimal
|
||||||
|
// prioritization, since the stream could be given a priority that is
|
||||||
|
// different from what is intended. To avoid these problems, an endpoint
|
||||||
|
// SHOULD retain stream prioritization state for a period after streams
|
||||||
|
// become closed. The longer state is retained, the lower the chance that
|
||||||
|
// streams are assigned incorrect or default priority values."
|
||||||
|
MaxClosedNodesInTree int
|
||||||
|
|
||||||
|
// MaxIdleNodesInTree controls the maximum number of idle streams to
|
||||||
|
// retain in the priority tree. Setting this to zero saves a small amount
|
||||||
|
// of memory at the cost of performance.
|
||||||
|
//
|
||||||
|
// See RFC 7540, Section 5.3.4:
|
||||||
|
// Similarly, streams that are in the "idle" state can be assigned
|
||||||
|
// priority or become a parent of other streams. This allows for the
|
||||||
|
// creation of a grouping node in the dependency tree, which enables
|
||||||
|
// more flexible expressions of priority. Idle streams begin with a
|
||||||
|
// default priority (Section 5.3.5).
|
||||||
|
MaxIdleNodesInTree int
|
||||||
|
|
||||||
|
// ThrottleOutOfOrderWrites enables write throttling to help ensure that
|
||||||
|
// data is delivered in priority order. This works around a race where
|
||||||
|
// stream B depends on stream A and both streams are about to call Write
|
||||||
|
// to queue DATA frames. If B wins the race, a naive scheduler would eagerly
|
||||||
|
// write as much data from B as possible, but this is suboptimal because A
|
||||||
|
// is a higher-priority stream. With throttling enabled, we write a small
|
||||||
|
// amount of data from B to minimize the amount of bandwidth that B can
|
||||||
|
// steal from A.
|
||||||
|
ThrottleOutOfOrderWrites bool
|
||||||
|
}
|
||||||
+2
-41
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -13,47 +15,6 @@ import (
|
|||||||
// RFC 7540, Section 5.3.5: the default weight is 16.
|
// RFC 7540, Section 5.3.5: the default weight is 16.
|
||||||
const priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
|
const priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
|
||||||
|
|
||||||
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
|
||||||
//
|
|
||||||
// Deprecated: User-provided write schedulers are deprecated.
|
|
||||||
type PriorityWriteSchedulerConfig struct {
|
|
||||||
// MaxClosedNodesInTree controls the maximum number of closed streams to
|
|
||||||
// retain in the priority tree. Setting this to zero saves a small amount
|
|
||||||
// of memory at the cost of performance.
|
|
||||||
//
|
|
||||||
// See RFC 7540, Section 5.3.4:
|
|
||||||
// "It is possible for a stream to become closed while prioritization
|
|
||||||
// information ... is in transit. ... This potentially creates suboptimal
|
|
||||||
// prioritization, since the stream could be given a priority that is
|
|
||||||
// different from what is intended. To avoid these problems, an endpoint
|
|
||||||
// SHOULD retain stream prioritization state for a period after streams
|
|
||||||
// become closed. The longer state is retained, the lower the chance that
|
|
||||||
// streams are assigned incorrect or default priority values."
|
|
||||||
MaxClosedNodesInTree int
|
|
||||||
|
|
||||||
// MaxIdleNodesInTree controls the maximum number of idle streams to
|
|
||||||
// retain in the priority tree. Setting this to zero saves a small amount
|
|
||||||
// of memory at the cost of performance.
|
|
||||||
//
|
|
||||||
// See RFC 7540, Section 5.3.4:
|
|
||||||
// Similarly, streams that are in the "idle" state can be assigned
|
|
||||||
// priority or become a parent of other streams. This allows for the
|
|
||||||
// creation of a grouping node in the dependency tree, which enables
|
|
||||||
// more flexible expressions of priority. Idle streams begin with a
|
|
||||||
// default priority (Section 5.3.5).
|
|
||||||
MaxIdleNodesInTree int
|
|
||||||
|
|
||||||
// ThrottleOutOfOrderWrites enables write throttling to help ensure that
|
|
||||||
// data is delivered in priority order. This works around a race where
|
|
||||||
// stream B depends on stream A and both streams are about to call Write
|
|
||||||
// to queue DATA frames. If B wins the race, a naive scheduler would eagerly
|
|
||||||
// write as much data from B as possible, but this is suboptimal because A
|
|
||||||
// is a higher-priority stream. With throttling enabled, we write a small
|
|
||||||
// amount of data from B to minimize the amount of bandwidth that B can
|
|
||||||
// steal from A.
|
|
||||||
ThrottleOutOfOrderWrites bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPriorityWriteScheduler constructs a WriteScheduler that schedules
|
// NewPriorityWriteScheduler constructs a WriteScheduler that schedules
|
||||||
// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3.
|
// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3.
|
||||||
// If cfg is nil, default options are used.
|
// If cfg is nil, default options are used.
|
||||||
|
|||||||
+2
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+2
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import "math"
|
import "math"
|
||||||
|
|||||||
+2
@@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !(go1.27 && !http2legacy)
|
||||||
|
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2021 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build go1.18
|
|
||||||
|
|
||||||
package idna
|
|
||||||
|
|
||||||
// Transitional processing is disabled by default in Go 1.18.
|
|
||||||
// https://golang.org/issue/47510
|
|
||||||
const transitionalLookup = false
|
|
||||||
Generated
Vendored
+148
-37
@@ -4,8 +4,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build go1.10
|
|
||||||
|
|
||||||
// Package idna implements IDNA2008 using the compatibility processing
|
// Package idna implements IDNA2008 using the compatibility processing
|
||||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
||||||
// deal with the transition from IDNA2003.
|
// deal with the transition from IDNA2003.
|
||||||
@@ -20,6 +18,7 @@ package idna // import "golang.org/x/net/idna"
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/secure/bidirule"
|
"golang.org/x/text/secure/bidirule"
|
||||||
@@ -27,6 +26,8 @@ import (
|
|||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const unicode16 = unicode.Version >= "16.0.0"
|
||||||
|
|
||||||
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
||||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
||||||
// evaluated string as lookup.
|
// evaluated string as lookup.
|
||||||
@@ -101,6 +102,11 @@ func ValidateLabels(enable bool) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateLabels reports whether the ValidateLabels option is enabled.
|
||||||
|
func (p *Profile) validateLabels() bool {
|
||||||
|
return p.fromPuny != nil
|
||||||
|
}
|
||||||
|
|
||||||
// CheckHyphens sets whether to check for correct use of hyphens ('-') in
|
// CheckHyphens sets whether to check for correct use of hyphens ('-') in
|
||||||
// labels. Most web browsers do not have this option set, since labels such as
|
// labels. Most web browsers do not have this option set, since labels such as
|
||||||
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
||||||
@@ -263,6 +269,10 @@ func (p *Profile) String() string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transitional processing is disabled by default as of Go 1.18.
|
||||||
|
// https://golang.org/issue/47510
|
||||||
|
const transitionalLookup = false
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
// Punycode is a Profile that does raw punycode processing with a minimum
|
||||||
// of validation.
|
// of validation.
|
||||||
@@ -324,15 +334,30 @@ func (e labelError) Error() string {
|
|||||||
return fmt.Sprintf("idna: invalid label %q", e.label)
|
return fmt.Sprintf("idna: invalid label %q", e.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
type runeError rune
|
type runeError struct {
|
||||||
|
r rune
|
||||||
func (e runeError) code() string { return "P1" }
|
code_ string
|
||||||
func (e runeError) Error() string {
|
|
||||||
return fmt.Sprintf("idna: disallowed rune %U", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// process implements the algorithm described in section 4 of UTS #46,
|
func (e runeError) code() string { return e.code_ }
|
||||||
// see https://www.unicode.org/reports/tr46.
|
func (e runeError) Error() string {
|
||||||
|
return fmt.Sprintf("idna: disallowed rune %U", e.r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// code16 returns old for Unicode < 16, new for Unicode >= 16.
|
||||||
|
func code16(old, new string) string {
|
||||||
|
if unicode16 {
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// process10 implements the algorithm described in section 4 of UTS #46.
|
||||||
|
// It implements both the Unicode 10 algorithm
|
||||||
|
// (https://www.unicode.org/reports/tr46/tr46-19.html)
|
||||||
|
// and the Unicode 16 algorithm
|
||||||
|
// (https://www.unicode.org/reports/tr46/tr46-35.html)
|
||||||
|
// depending on unicode16, which in turn depends on unicode.Version.
|
||||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
var isBidi bool
|
var isBidi bool
|
||||||
@@ -347,8 +372,12 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|||||||
// TODO: allow for a quick check of the tables data.
|
// TODO: allow for a quick check of the tables data.
|
||||||
// It seems like we should only create this error on ToASCII, but the
|
// It seems like we should only create this error on ToASCII, but the
|
||||||
// UTS 46 conformance tests suggests we should always check this.
|
// UTS 46 conformance tests suggests we should always check this.
|
||||||
|
labelCode := "X4_2"
|
||||||
|
if !unicode16 || toASCII {
|
||||||
|
labelCode = "A4"
|
||||||
|
}
|
||||||
if err == nil && p.verifyDNSLength && s == "" {
|
if err == nil && p.verifyDNSLength && s == "" {
|
||||||
err = &labelError{s, "A4"}
|
err = labelError{s, labelCode}
|
||||||
}
|
}
|
||||||
labels := labelIter{orig: s}
|
labels := labelIter{orig: s}
|
||||||
for ; !labels.done(); labels.next() {
|
for ; !labels.done(); labels.next() {
|
||||||
@@ -357,12 +386,13 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|||||||
// Empty labels are not okay. The label iterator skips the last
|
// Empty labels are not okay. The label iterator skips the last
|
||||||
// label if it is empty.
|
// label if it is empty.
|
||||||
if err == nil && p.verifyDNSLength {
|
if err == nil && p.verifyDNSLength {
|
||||||
err = &labelError{s, "A4"}
|
err = labelError{s, labelCode}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(label, acePrefix) {
|
if strings.HasPrefix(label, acePrefix) {
|
||||||
u, err2 := decode(label[len(acePrefix):])
|
enc := label[len(acePrefix):]
|
||||||
|
u, err2 := decode(enc)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = err2
|
err = err2
|
||||||
@@ -370,6 +400,9 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|||||||
// Spec says keep the old label.
|
// Spec says keep the old label.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if unicode16 && err == nil && len(u) > 0 && isASCII(u) {
|
||||||
|
err = punyError(enc)
|
||||||
|
}
|
||||||
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
||||||
labels.set(u)
|
labels.set(u)
|
||||||
if err == nil && p.fromPuny != nil {
|
if err == nil && p.fromPuny != nil {
|
||||||
@@ -379,16 +412,16 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|||||||
// This should be called on NonTransitional, according to the
|
// This should be called on NonTransitional, according to the
|
||||||
// spec, but that currently does not have any effect. Use the
|
// spec, but that currently does not have any effect. Use the
|
||||||
// original profile to preserve options.
|
// original profile to preserve options.
|
||||||
err = p.validateLabel(u)
|
err = p.validateLabel(u, labelCode)
|
||||||
}
|
}
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
err = p.validateLabel(label)
|
err = p.validateLabel(label, labelCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isBidi && p.bidirule != nil && err == nil {
|
if isBidi && p.bidirule != nil && err == nil {
|
||||||
for labels.reset(); !labels.done(); labels.next() {
|
for labels.reset(); !labels.done(); labels.next() {
|
||||||
if !p.bidirule(labels.label()) {
|
if !p.bidirule(labels.label()) {
|
||||||
err = &labelError{s, "B"}
|
err = labelError{s, "B"}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,24 +439,36 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|||||||
}
|
}
|
||||||
n := len(label)
|
n := len(label)
|
||||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
|
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
|
||||||
err = &labelError{label, "A4"}
|
err = labelError{label, labelCode}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = labels.result()
|
s = labels.result()
|
||||||
if toASCII && p.verifyDNSLength && err == nil {
|
if toASCII && p.verifyDNSLength && err == nil {
|
||||||
|
if unicode16 && strings.HasSuffix(s, ".") {
|
||||||
|
err = labelError{s, labelCode}
|
||||||
|
}
|
||||||
// Compute the length of the domain name minus the root label and its dot.
|
// Compute the length of the domain name minus the root label and its dot.
|
||||||
n := len(s)
|
n := len(s)
|
||||||
if n > 0 && s[n-1] == '.' {
|
if n > 0 && s[n-1] == '.' {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
if len(s) < 1 || n > 253 {
|
if len(s) < 1 || n > 253 {
|
||||||
err = &labelError{s, "A4"}
|
err = labelError{s, labelCode}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isASCII(s string) bool {
|
||||||
|
for _, c := range []byte(s) {
|
||||||
|
if c >= 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
|
func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
|
||||||
// TODO: consider first doing a quick check to see if any of these checks
|
// TODO: consider first doing a quick check to see if any of these checks
|
||||||
// need to be done. This will make it slower in the general case, but
|
// need to be done. This will make it slower in the general case, but
|
||||||
@@ -436,12 +481,12 @@ func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
|
|||||||
func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
|
func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
|
||||||
// TODO: filter need for normalization in loop below.
|
// TODO: filter need for normalization in loop below.
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return s, false, &labelError{s, "V1"}
|
return s, false, labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
if sz == 0 {
|
if sz == 0 {
|
||||||
return s, bidi, runeError(utf8.RuneError)
|
return s, bidi, runeError{utf8.RuneError, "P1"}
|
||||||
}
|
}
|
||||||
bidi = bidi || info(v).isBidi(s[i:])
|
bidi = bidi || info(v).isBidi(s[i:])
|
||||||
// Copy bytes not copied so far.
|
// Copy bytes not copied so far.
|
||||||
@@ -449,9 +494,12 @@ func validateRegistration(p *Profile, s string) (idem string, bidi bool, err err
|
|||||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||||
// for strict conformance to IDNA2008.
|
// for strict conformance to IDNA2008.
|
||||||
case valid, deviation:
|
case valid, deviation:
|
||||||
|
if sz == 1 && p.useSTD3Rules && !allowedSTD3(rune(s[i])) {
|
||||||
|
return s, bidi, runeError{rune(s[i]), "P1"}
|
||||||
|
}
|
||||||
case disallowed, mapped, unknown, ignored:
|
case disallowed, mapped, unknown, ignored:
|
||||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
r, _ := utf8.DecodeRuneInString(s[i:])
|
||||||
return s, bidi, runeError(r)
|
return s, bidi, runeError{r, "P1"}
|
||||||
}
|
}
|
||||||
i += sz
|
i += sz
|
||||||
}
|
}
|
||||||
@@ -489,7 +537,7 @@ func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
|||||||
b = append(b, "\ufffd"...)
|
b = append(b, "\ufffd"...)
|
||||||
k = len(s)
|
k = len(s)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = runeError(utf8.RuneError)
|
err = runeError{utf8.RuneError, "P1"}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -502,14 +550,26 @@ func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
|||||||
case valid:
|
case valid:
|
||||||
continue
|
continue
|
||||||
case disallowed:
|
case disallowed:
|
||||||
if err == nil {
|
// Unicode 16 delays the error until validateLabels.
|
||||||
|
// Unicode 10 gave an error now.
|
||||||
|
if !unicode16 && err == nil {
|
||||||
r, _ := utf8.DecodeRuneInString(s[start:])
|
r, _ := utf8.DecodeRuneInString(s[start:])
|
||||||
err = runeError(r)
|
err = runeError{r, "P1"}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case mapped, deviation:
|
case deviation:
|
||||||
|
if unicode16 && !p.transitional {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case mapped:
|
||||||
b = append(b, s[k:start]...)
|
b = append(b, s[k:start]...)
|
||||||
b = info(v).appendMapping(b, s[start:i])
|
// Unicode 16 requires a special case to handle ẞ -> ss in transitional mode.
|
||||||
|
if unicode16 && p.transitional && s[start:start+sz] == "ẞ" {
|
||||||
|
b = append(b, "ss"...)
|
||||||
|
} else {
|
||||||
|
b = info(v).appendMapping(b, s[start:i])
|
||||||
|
}
|
||||||
case ignored:
|
case ignored:
|
||||||
b = append(b, s[k:start]...)
|
b = append(b, s[k:start]...)
|
||||||
// drop the rune
|
// drop the rune
|
||||||
@@ -600,13 +660,13 @@ const acePrefix = "xn--"
|
|||||||
|
|
||||||
func (p *Profile) simplify(cat category) category {
|
func (p *Profile) simplify(cat category) category {
|
||||||
switch cat {
|
switch cat {
|
||||||
case disallowedSTD3Mapped:
|
case disallowedSTD3Mapped: // only happens for pre-Unicode 16
|
||||||
if p.useSTD3Rules {
|
if p.useSTD3Rules {
|
||||||
cat = disallowed
|
cat = disallowed
|
||||||
} else {
|
} else {
|
||||||
cat = mapped
|
cat = mapped
|
||||||
}
|
}
|
||||||
case disallowedSTD3Valid:
|
case disallowedSTD3Valid: // only happens for pre-Unicode 16
|
||||||
if p.useSTD3Rules {
|
if p.useSTD3Rules {
|
||||||
cat = disallowed
|
cat = disallowed
|
||||||
} else {
|
} else {
|
||||||
@@ -625,17 +685,18 @@ func (p *Profile) simplify(cat category) category {
|
|||||||
|
|
||||||
func validateFromPunycode(p *Profile, s string) error {
|
func validateFromPunycode(p *Profile, s string) error {
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return &labelError{s, "V1"}
|
return labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
// TODO: detect whether string may have to be normalized in the following
|
// TODO: detect whether string may have to be normalized in the following
|
||||||
// loop.
|
// loop.
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
if sz == 0 {
|
if sz == 0 {
|
||||||
return runeError(utf8.RuneError)
|
return runeError{utf8.RuneError, "P1"}
|
||||||
}
|
}
|
||||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
cat := info(v).category()
|
||||||
return &labelError{s, "V6"}
|
if c := p.simplify(cat); c != valid && c != deviation {
|
||||||
|
return labelError{s, code16("V6", "V7")}
|
||||||
}
|
}
|
||||||
i += sz
|
i += sz
|
||||||
}
|
}
|
||||||
@@ -704,23 +765,51 @@ var joinStates = [][numJoinTypes]joinState{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowedSTD3 reports whether r is a rune that can appear in a domain name
|
||||||
|
// according to STD3. We allow all non-ASCII runes and then letters, digits, hyphens.
|
||||||
|
// We also add dot so that this can be run against the whole name and not just
|
||||||
|
// a single name element (label). The surrounding code checks dots well enough.
|
||||||
|
func allowedSTD3(r rune) bool {
|
||||||
|
return r >= 0x80 || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '-' || r == '.'
|
||||||
|
}
|
||||||
|
|
||||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||||
// already implicitly satisfied by the overall implementation.
|
// already implicitly satisfied by the overall implementation.
|
||||||
func (p *Profile) validateLabel(s string) (err error) {
|
func (p *Profile) validateLabel(s string, labelCode string) (err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
if p.verifyDNSLength {
|
if p.verifyDNSLength {
|
||||||
return &labelError{s, "A4"}
|
return labelError{s, labelCode}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.checkHyphens {
|
if p.checkHyphens {
|
||||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
||||||
return &labelError{s, "V2"}
|
return labelError{s, "V2"}
|
||||||
}
|
}
|
||||||
if s[0] == '-' || s[len(s)-1] == '-' {
|
if s[0] == '-' || s[len(s)-1] == '-' {
|
||||||
return &labelError{s, "V3"}
|
return labelError{s, "V3"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unicode 16's TR 46 delays the rune validity checks until after the label is decoded.
|
||||||
|
// (validateAndMap did not reject them earlier.)
|
||||||
|
if unicode16 && p.validateLabels() {
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
return runeError{utf8.RuneError, "P1"}
|
||||||
|
}
|
||||||
|
cat := info(v).category()
|
||||||
|
if c := p.simplify(cat); c != valid && (!p.transitional || c != deviation) {
|
||||||
|
return labelError{s, "V7"}
|
||||||
|
}
|
||||||
|
if sz == 1 && p.useSTD3Rules && !allowedSTD3(rune(s[i])) {
|
||||||
|
return runeError{rune(s[i]), "U1"}
|
||||||
|
}
|
||||||
|
i += sz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !p.checkJoiners {
|
if !p.checkJoiners {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -729,7 +818,7 @@ func (p *Profile) validateLabel(s string) (err error) {
|
|||||||
v, sz := trie.lookupString(s)
|
v, sz := trie.lookupString(s)
|
||||||
x := info(v)
|
x := info(v)
|
||||||
if x.isModifier() {
|
if x.isModifier() {
|
||||||
return &labelError{s, "V5"}
|
return labelError{s, code16("V5", "V6")}
|
||||||
}
|
}
|
||||||
// Quickly return in the absence of zero-width (non) joiners.
|
// Quickly return in the absence of zero-width (non) joiners.
|
||||||
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
|
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
|
||||||
@@ -754,8 +843,9 @@ func (p *Profile) validateLabel(s string) (err error) {
|
|||||||
x = info(v)
|
x = info(v)
|
||||||
}
|
}
|
||||||
if st == stateFAIL || st == stateAfter {
|
if st == stateFAIL || st == stateAfter {
|
||||||
return &labelError{s, "C"}
|
return labelError{s, "C"}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,3 +857,24 @@ func ascii(s string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendMapping appends the mapping for the respective rune. isMapped must be
|
||||||
|
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
||||||
|
func (c info) appendMapping(b []byte, s string) []byte {
|
||||||
|
index := int(c >> indexShift)
|
||||||
|
if c&xorBit == 0 {
|
||||||
|
p := index
|
||||||
|
return append(b, mappings[mappingIndex[p]:mappingIndex[p+1]]...)
|
||||||
|
}
|
||||||
|
b = append(b, s...)
|
||||||
|
if c&inlineXOR == inlineXOR {
|
||||||
|
// TODO: support and handle two-byte inline masks
|
||||||
|
b[len(b)-1] ^= byte(index)
|
||||||
|
} else {
|
||||||
|
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
||||||
|
index++
|
||||||
|
b[p] ^= xorData[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
-717
@@ -1,717 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !go1.10
|
|
||||||
|
|
||||||
// Package idna implements IDNA2008 using the compatibility processing
|
|
||||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
|
||||||
// deal with the transition from IDNA2003.
|
|
||||||
//
|
|
||||||
// IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
|
|
||||||
// 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
|
|
||||||
// UTS #46 is defined in https://www.unicode.org/reports/tr46.
|
|
||||||
// See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
|
||||||
// differences between these two standards.
|
|
||||||
package idna // import "golang.org/x/net/idna"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/secure/bidirule"
|
|
||||||
"golang.org/x/text/unicode/norm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
|
||||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
|
||||||
// evaluated string as lookup.
|
|
||||||
// TODO: the current error handling is, in my opinion, the least opinionated.
|
|
||||||
// Other strategies are also viable, though:
|
|
||||||
// Option 1) Return an empty string in case of error, but allow the user to
|
|
||||||
// specify explicitly which errors to ignore.
|
|
||||||
// Option 2) Return the partially evaluated string if it is itself a valid
|
|
||||||
// string, otherwise return the empty string in case of error.
|
|
||||||
// Option 3) Option 1 and 2.
|
|
||||||
// Option 4) Always return an empty string for now and implement Option 1 as
|
|
||||||
// needed, and document that the return string may not be empty in case of
|
|
||||||
// error in the future.
|
|
||||||
// I think Option 1 is best, but it is quite opinionated.
|
|
||||||
|
|
||||||
// ToASCII is a wrapper for Punycode.ToASCII.
|
|
||||||
func ToASCII(s string) (string, error) {
|
|
||||||
return Punycode.process(s, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToUnicode is a wrapper for Punycode.ToUnicode.
|
|
||||||
func ToUnicode(s string) (string, error) {
|
|
||||||
return Punycode.process(s, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Option configures a Profile at creation time.
|
|
||||||
type Option func(*options)
|
|
||||||
|
|
||||||
// Transitional sets a Profile to use the Transitional mapping as defined in UTS
|
|
||||||
// #46. This will cause, for example, "ß" to be mapped to "ss". Using the
|
|
||||||
// transitional mapping provides a compromise between IDNA2003 and IDNA2008
|
|
||||||
// compatibility. It is used by some browsers when resolving domain names. This
|
|
||||||
// option is only meaningful if combined with MapForLookup.
|
|
||||||
func Transitional(transitional bool) Option {
|
|
||||||
return func(o *options) { o.transitional = transitional }
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
|
|
||||||
// are longer than allowed by the RFC.
|
|
||||||
//
|
|
||||||
// This option corresponds to the VerifyDnsLength flag in UTS #46.
|
|
||||||
func VerifyDNSLength(verify bool) Option {
|
|
||||||
return func(o *options) { o.verifyDNSLength = verify }
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
|
||||||
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
|
||||||
func RemoveLeadingDots(remove bool) Option {
|
|
||||||
return func(o *options) { o.removeLeadingDots = remove }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
|
||||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
|
||||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
|
||||||
// In particular, ValidateLabels also sets the CheckHyphens and CheckJoiners flags
|
|
||||||
// in UTS #46.
|
|
||||||
func ValidateLabels(enable bool) Option {
|
|
||||||
return func(o *options) {
|
|
||||||
// Don't override existing mappings, but set one that at least checks
|
|
||||||
// normalization if it is not set.
|
|
||||||
if o.mapping == nil && enable {
|
|
||||||
o.mapping = normalize
|
|
||||||
}
|
|
||||||
o.trie = trie
|
|
||||||
o.checkJoiners = enable
|
|
||||||
o.checkHyphens = enable
|
|
||||||
if enable {
|
|
||||||
o.fromPuny = validateFromPunycode
|
|
||||||
} else {
|
|
||||||
o.fromPuny = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckHyphens sets whether to check for correct use of hyphens ('-') in
|
|
||||||
// labels. Most web browsers do not have this option set, since labels such as
|
|
||||||
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
|
||||||
//
|
|
||||||
// This option corresponds to the CheckHyphens flag in UTS #46.
|
|
||||||
func CheckHyphens(enable bool) Option {
|
|
||||||
return func(o *options) { o.checkHyphens = enable }
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckJoiners sets whether to check the ContextJ rules as defined in Appendix
|
|
||||||
// A of RFC 5892, concerning the use of joiner runes.
|
|
||||||
//
|
|
||||||
// This option corresponds to the CheckJoiners flag in UTS #46.
|
|
||||||
func CheckJoiners(enable bool) Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.trie = trie
|
|
||||||
o.checkJoiners = enable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictDomainName limits the set of permissible ASCII characters to those
|
|
||||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
|
||||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration,
|
|
||||||
// but is only useful if ValidateLabels is set.
|
|
||||||
//
|
|
||||||
// This option is useful, for instance, for browsers that allow characters
|
|
||||||
// outside this range, for example a '_' (U+005F LOW LINE). See
|
|
||||||
// http://www.rfc-editor.org/std/std3.txt for more details.
|
|
||||||
//
|
|
||||||
// This option corresponds to the UseSTD3ASCIIRules flag in UTS #46.
|
|
||||||
func StrictDomainName(use bool) Option {
|
|
||||||
return func(o *options) { o.useSTD3Rules = use }
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: the following options pull in tables. The tables should not be linked
|
|
||||||
// in as long as the options are not used.
|
|
||||||
|
|
||||||
// BidiRule enables the Bidi rule as defined in RFC 5893. Any application
|
|
||||||
// that relies on proper validation of labels should include this rule.
|
|
||||||
//
|
|
||||||
// This option corresponds to the CheckBidi flag in UTS #46.
|
|
||||||
func BidiRule() Option {
|
|
||||||
return func(o *options) { o.bidirule = bidirule.ValidString }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateForRegistration sets validation options to verify that a given IDN is
|
|
||||||
// properly formatted for registration as defined by Section 4 of RFC 5891.
|
|
||||||
func ValidateForRegistration() Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.mapping = validateRegistration
|
|
||||||
StrictDomainName(true)(o)
|
|
||||||
ValidateLabels(true)(o)
|
|
||||||
VerifyDNSLength(true)(o)
|
|
||||||
BidiRule()(o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapForLookup sets validation and mapping options such that a given IDN is
|
|
||||||
// transformed for domain name lookup according to the requirements set out in
|
|
||||||
// Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
|
|
||||||
// RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
|
|
||||||
// to add this check.
|
|
||||||
//
|
|
||||||
// The mappings include normalization and mapping case, width and other
|
|
||||||
// compatibility mappings.
|
|
||||||
func MapForLookup() Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.mapping = validateAndMap
|
|
||||||
StrictDomainName(true)(o)
|
|
||||||
ValidateLabels(true)(o)
|
|
||||||
RemoveLeadingDots(true)(o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type options struct {
|
|
||||||
transitional bool
|
|
||||||
useSTD3Rules bool
|
|
||||||
checkHyphens bool
|
|
||||||
checkJoiners bool
|
|
||||||
verifyDNSLength bool
|
|
||||||
removeLeadingDots bool
|
|
||||||
|
|
||||||
trie *idnaTrie
|
|
||||||
|
|
||||||
// fromPuny calls validation rules when converting A-labels to U-labels.
|
|
||||||
fromPuny func(p *Profile, s string) error
|
|
||||||
|
|
||||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
|
||||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
|
||||||
mapping func(p *Profile, s string) (string, error)
|
|
||||||
|
|
||||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
|
||||||
// defined in RFC 5893.
|
|
||||||
bidirule func(s string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Profile defines the configuration of a IDNA mapper.
|
|
||||||
type Profile struct {
|
|
||||||
options
|
|
||||||
}
|
|
||||||
|
|
||||||
func apply(o *options, opts []Option) {
|
|
||||||
for _, f := range opts {
|
|
||||||
f(o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Profile.
|
|
||||||
//
|
|
||||||
// With no options, the returned Profile is the most permissive and equals the
|
|
||||||
// Punycode Profile. Options can be passed to further restrict the Profile. The
|
|
||||||
// MapForLookup and ValidateForRegistration options set a collection of options,
|
|
||||||
// for lookup and registration purposes respectively, which can be tailored by
|
|
||||||
// adding more fine-grained options, where later options override earlier
|
|
||||||
// options.
|
|
||||||
func New(o ...Option) *Profile {
|
|
||||||
p := &Profile{}
|
|
||||||
apply(&p.options, o)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToASCII converts a domain or domain label to its ASCII form. For example,
|
|
||||||
// ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
|
||||||
// ToASCII("golang") is "golang". If an error is encountered it will return
|
|
||||||
// an error and a (partially) processed result.
|
|
||||||
func (p *Profile) ToASCII(s string) (string, error) {
|
|
||||||
return p.process(s, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToUnicode converts a domain or domain label to its Unicode form. For example,
|
|
||||||
// ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
|
|
||||||
// ToUnicode("golang") is "golang". If an error is encountered it will return
|
|
||||||
// an error and a (partially) processed result.
|
|
||||||
func (p *Profile) ToUnicode(s string) (string, error) {
|
|
||||||
pp := *p
|
|
||||||
pp.transitional = false
|
|
||||||
return pp.process(s, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String reports a string with a description of the profile for debugging
|
|
||||||
// purposes. The string format may change with different versions.
|
|
||||||
func (p *Profile) String() string {
|
|
||||||
s := ""
|
|
||||||
if p.transitional {
|
|
||||||
s = "Transitional"
|
|
||||||
} else {
|
|
||||||
s = "NonTransitional"
|
|
||||||
}
|
|
||||||
if p.useSTD3Rules {
|
|
||||||
s += ":UseSTD3Rules"
|
|
||||||
}
|
|
||||||
if p.checkHyphens {
|
|
||||||
s += ":CheckHyphens"
|
|
||||||
}
|
|
||||||
if p.checkJoiners {
|
|
||||||
s += ":CheckJoiners"
|
|
||||||
}
|
|
||||||
if p.verifyDNSLength {
|
|
||||||
s += ":VerifyDNSLength"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
|
||||||
// of validation.
|
|
||||||
Punycode *Profile = punycode
|
|
||||||
|
|
||||||
// Lookup is the recommended profile for looking up domain names, according
|
|
||||||
// to Section 5 of RFC 5891. The exact configuration of this profile may
|
|
||||||
// change over time.
|
|
||||||
Lookup *Profile = lookup
|
|
||||||
|
|
||||||
// Display is the recommended profile for displaying domain names.
|
|
||||||
// The configuration of this profile may change over time.
|
|
||||||
Display *Profile = display
|
|
||||||
|
|
||||||
// Registration is the recommended profile for checking whether a given
|
|
||||||
// IDN is valid for registration, according to Section 4 of RFC 5891.
|
|
||||||
Registration *Profile = registration
|
|
||||||
|
|
||||||
punycode = &Profile{}
|
|
||||||
lookup = &Profile{options{
|
|
||||||
transitional: true,
|
|
||||||
removeLeadingDots: true,
|
|
||||||
useSTD3Rules: true,
|
|
||||||
checkHyphens: true,
|
|
||||||
checkJoiners: true,
|
|
||||||
trie: trie,
|
|
||||||
fromPuny: validateFromPunycode,
|
|
||||||
mapping: validateAndMap,
|
|
||||||
bidirule: bidirule.ValidString,
|
|
||||||
}}
|
|
||||||
display = &Profile{options{
|
|
||||||
useSTD3Rules: true,
|
|
||||||
removeLeadingDots: true,
|
|
||||||
checkHyphens: true,
|
|
||||||
checkJoiners: true,
|
|
||||||
trie: trie,
|
|
||||||
fromPuny: validateFromPunycode,
|
|
||||||
mapping: validateAndMap,
|
|
||||||
bidirule: bidirule.ValidString,
|
|
||||||
}}
|
|
||||||
registration = &Profile{options{
|
|
||||||
useSTD3Rules: true,
|
|
||||||
verifyDNSLength: true,
|
|
||||||
checkHyphens: true,
|
|
||||||
checkJoiners: true,
|
|
||||||
trie: trie,
|
|
||||||
fromPuny: validateFromPunycode,
|
|
||||||
mapping: validateRegistration,
|
|
||||||
bidirule: bidirule.ValidString,
|
|
||||||
}}
|
|
||||||
|
|
||||||
// TODO: profiles
|
|
||||||
// Register: recommended for approving domain names: don't do any mappings
|
|
||||||
// but rather reject on invalid input. Bundle or block deviation characters.
|
|
||||||
)
|
|
||||||
|
|
||||||
type labelError struct{ label, code_ string }
|
|
||||||
|
|
||||||
func (e labelError) code() string { return e.code_ }
|
|
||||||
func (e labelError) Error() string {
|
|
||||||
return fmt.Sprintf("idna: invalid label %q", e.label)
|
|
||||||
}
|
|
||||||
|
|
||||||
type runeError rune
|
|
||||||
|
|
||||||
func (e runeError) code() string { return "P1" }
|
|
||||||
func (e runeError) Error() string {
|
|
||||||
return fmt.Sprintf("idna: disallowed rune %U", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// process implements the algorithm described in section 4 of UTS #46,
|
|
||||||
// see https://www.unicode.org/reports/tr46.
|
|
||||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
|
||||||
var err error
|
|
||||||
if p.mapping != nil {
|
|
||||||
s, err = p.mapping(p, s)
|
|
||||||
}
|
|
||||||
// Remove leading empty labels.
|
|
||||||
if p.removeLeadingDots {
|
|
||||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// It seems like we should only create this error on ToASCII, but the
|
|
||||||
// UTS 46 conformance tests suggests we should always check this.
|
|
||||||
if err == nil && p.verifyDNSLength && s == "" {
|
|
||||||
err = &labelError{s, "A4"}
|
|
||||||
}
|
|
||||||
labels := labelIter{orig: s}
|
|
||||||
for ; !labels.done(); labels.next() {
|
|
||||||
label := labels.label()
|
|
||||||
if label == "" {
|
|
||||||
// Empty labels are not okay. The label iterator skips the last
|
|
||||||
// label if it is empty.
|
|
||||||
if err == nil && p.verifyDNSLength {
|
|
||||||
err = &labelError{s, "A4"}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(label, acePrefix) {
|
|
||||||
u, err2 := decode(label[len(acePrefix):])
|
|
||||||
if err2 != nil {
|
|
||||||
if err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
// Spec says keep the old label.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
labels.set(u)
|
|
||||||
if err == nil && p.fromPuny != nil {
|
|
||||||
err = p.fromPuny(p, u)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
// This should be called on NonTransitional, according to the
|
|
||||||
// spec, but that currently does not have any effect. Use the
|
|
||||||
// original profile to preserve options.
|
|
||||||
err = p.validateLabel(u)
|
|
||||||
}
|
|
||||||
} else if err == nil {
|
|
||||||
err = p.validateLabel(label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if toASCII {
|
|
||||||
for labels.reset(); !labels.done(); labels.next() {
|
|
||||||
label := labels.label()
|
|
||||||
if !ascii(label) {
|
|
||||||
a, err2 := encode(acePrefix, label)
|
|
||||||
if err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
label = a
|
|
||||||
labels.set(a)
|
|
||||||
}
|
|
||||||
n := len(label)
|
|
||||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
|
|
||||||
err = &labelError{label, "A4"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = labels.result()
|
|
||||||
if toASCII && p.verifyDNSLength && err == nil {
|
|
||||||
// Compute the length of the domain name minus the root label and its dot.
|
|
||||||
n := len(s)
|
|
||||||
if n > 0 && s[n-1] == '.' {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
if len(s) < 1 || n > 253 {
|
|
||||||
err = &labelError{s, "A4"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize(p *Profile, s string) (string, error) {
|
|
||||||
return norm.NFC.String(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateRegistration(p *Profile, s string) (string, error) {
|
|
||||||
if !norm.NFC.IsNormalString(s) {
|
|
||||||
return s, &labelError{s, "V1"}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(s); {
|
|
||||||
v, sz := trie.lookupString(s[i:])
|
|
||||||
// Copy bytes not copied so far.
|
|
||||||
switch p.simplify(info(v).category()) {
|
|
||||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
|
||||||
// for strict conformance to IDNA2008.
|
|
||||||
case valid, deviation:
|
|
||||||
case disallowed, mapped, unknown, ignored:
|
|
||||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
|
||||||
return s, runeError(r)
|
|
||||||
}
|
|
||||||
i += sz
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAndMap(p *Profile, s string) (string, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
b []byte
|
|
||||||
k int
|
|
||||||
)
|
|
||||||
for i := 0; i < len(s); {
|
|
||||||
v, sz := trie.lookupString(s[i:])
|
|
||||||
start := i
|
|
||||||
i += sz
|
|
||||||
// Copy bytes not copied so far.
|
|
||||||
switch p.simplify(info(v).category()) {
|
|
||||||
case valid:
|
|
||||||
continue
|
|
||||||
case disallowed:
|
|
||||||
if err == nil {
|
|
||||||
r, _ := utf8.DecodeRuneInString(s[start:])
|
|
||||||
err = runeError(r)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
case mapped, deviation:
|
|
||||||
b = append(b, s[k:start]...)
|
|
||||||
b = info(v).appendMapping(b, s[start:i])
|
|
||||||
case ignored:
|
|
||||||
b = append(b, s[k:start]...)
|
|
||||||
// drop the rune
|
|
||||||
case unknown:
|
|
||||||
b = append(b, s[k:start]...)
|
|
||||||
b = append(b, "\ufffd"...)
|
|
||||||
}
|
|
||||||
k = i
|
|
||||||
}
|
|
||||||
if k == 0 {
|
|
||||||
// No changes so far.
|
|
||||||
s = norm.NFC.String(s)
|
|
||||||
} else {
|
|
||||||
b = append(b, s[k:]...)
|
|
||||||
if norm.NFC.QuickSpan(b) != len(b) {
|
|
||||||
b = norm.NFC.Bytes(b)
|
|
||||||
}
|
|
||||||
// TODO: the punycode converters require strings as input.
|
|
||||||
s = string(b)
|
|
||||||
}
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// A labelIter allows iterating over domain name labels.
|
|
||||||
type labelIter struct {
|
|
||||||
orig string
|
|
||||||
slice []string
|
|
||||||
curStart int
|
|
||||||
curEnd int
|
|
||||||
i int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelIter) reset() {
|
|
||||||
l.curStart = 0
|
|
||||||
l.curEnd = 0
|
|
||||||
l.i = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelIter) done() bool {
|
|
||||||
return l.curStart >= len(l.orig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelIter) result() string {
|
|
||||||
if l.slice != nil {
|
|
||||||
return strings.Join(l.slice, ".")
|
|
||||||
}
|
|
||||||
return l.orig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelIter) label() string {
|
|
||||||
if l.slice != nil {
|
|
||||||
return l.slice[l.i]
|
|
||||||
}
|
|
||||||
p := strings.IndexByte(l.orig[l.curStart:], '.')
|
|
||||||
l.curEnd = l.curStart + p
|
|
||||||
if p == -1 {
|
|
||||||
l.curEnd = len(l.orig)
|
|
||||||
}
|
|
||||||
return l.orig[l.curStart:l.curEnd]
|
|
||||||
}
|
|
||||||
|
|
||||||
// next sets the value to the next label. It skips the last label if it is empty.
|
|
||||||
func (l *labelIter) next() {
|
|
||||||
l.i++
|
|
||||||
if l.slice != nil {
|
|
||||||
if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" {
|
|
||||||
l.curStart = len(l.orig)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
l.curStart = l.curEnd + 1
|
|
||||||
if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' {
|
|
||||||
l.curStart = len(l.orig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *labelIter) set(s string) {
|
|
||||||
if l.slice == nil {
|
|
||||||
l.slice = strings.Split(l.orig, ".")
|
|
||||||
}
|
|
||||||
l.slice[l.i] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
|
||||||
const acePrefix = "xn--"
|
|
||||||
|
|
||||||
func (p *Profile) simplify(cat category) category {
|
|
||||||
switch cat {
|
|
||||||
case disallowedSTD3Mapped:
|
|
||||||
if p.useSTD3Rules {
|
|
||||||
cat = disallowed
|
|
||||||
} else {
|
|
||||||
cat = mapped
|
|
||||||
}
|
|
||||||
case disallowedSTD3Valid:
|
|
||||||
if p.useSTD3Rules {
|
|
||||||
cat = disallowed
|
|
||||||
} else {
|
|
||||||
cat = valid
|
|
||||||
}
|
|
||||||
case deviation:
|
|
||||||
if !p.transitional {
|
|
||||||
cat = valid
|
|
||||||
}
|
|
||||||
case validNV8, validXV8:
|
|
||||||
// TODO: handle V2008
|
|
||||||
cat = valid
|
|
||||||
}
|
|
||||||
return cat
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateFromPunycode(p *Profile, s string) error {
|
|
||||||
if !norm.NFC.IsNormalString(s) {
|
|
||||||
return &labelError{s, "V1"}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(s); {
|
|
||||||
v, sz := trie.lookupString(s[i:])
|
|
||||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
|
||||||
return &labelError{s, "V6"}
|
|
||||||
}
|
|
||||||
i += sz
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
zwnj = "\u200c"
|
|
||||||
zwj = "\u200d"
|
|
||||||
)
|
|
||||||
|
|
||||||
type joinState int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
stateStart joinState = iota
|
|
||||||
stateVirama
|
|
||||||
stateBefore
|
|
||||||
stateBeforeVirama
|
|
||||||
stateAfter
|
|
||||||
stateFAIL
|
|
||||||
)
|
|
||||||
|
|
||||||
var joinStates = [][numJoinTypes]joinState{
|
|
||||||
stateStart: {
|
|
||||||
joiningL: stateBefore,
|
|
||||||
joiningD: stateBefore,
|
|
||||||
joinZWNJ: stateFAIL,
|
|
||||||
joinZWJ: stateFAIL,
|
|
||||||
joinVirama: stateVirama,
|
|
||||||
},
|
|
||||||
stateVirama: {
|
|
||||||
joiningL: stateBefore,
|
|
||||||
joiningD: stateBefore,
|
|
||||||
},
|
|
||||||
stateBefore: {
|
|
||||||
joiningL: stateBefore,
|
|
||||||
joiningD: stateBefore,
|
|
||||||
joiningT: stateBefore,
|
|
||||||
joinZWNJ: stateAfter,
|
|
||||||
joinZWJ: stateFAIL,
|
|
||||||
joinVirama: stateBeforeVirama,
|
|
||||||
},
|
|
||||||
stateBeforeVirama: {
|
|
||||||
joiningL: stateBefore,
|
|
||||||
joiningD: stateBefore,
|
|
||||||
joiningT: stateBefore,
|
|
||||||
},
|
|
||||||
stateAfter: {
|
|
||||||
joiningL: stateFAIL,
|
|
||||||
joiningD: stateBefore,
|
|
||||||
joiningT: stateAfter,
|
|
||||||
joiningR: stateStart,
|
|
||||||
joinZWNJ: stateFAIL,
|
|
||||||
joinZWJ: stateFAIL,
|
|
||||||
joinVirama: stateAfter, // no-op as we can't accept joiners here
|
|
||||||
},
|
|
||||||
stateFAIL: {
|
|
||||||
0: stateFAIL,
|
|
||||||
joiningL: stateFAIL,
|
|
||||||
joiningD: stateFAIL,
|
|
||||||
joiningT: stateFAIL,
|
|
||||||
joiningR: stateFAIL,
|
|
||||||
joinZWNJ: stateFAIL,
|
|
||||||
joinZWJ: stateFAIL,
|
|
||||||
joinVirama: stateFAIL,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
|
||||||
// already implicitly satisfied by the overall implementation.
|
|
||||||
func (p *Profile) validateLabel(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
if p.verifyDNSLength {
|
|
||||||
return &labelError{s, "A4"}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p.bidirule != nil && !p.bidirule(s) {
|
|
||||||
return &labelError{s, "B"}
|
|
||||||
}
|
|
||||||
if p.checkHyphens {
|
|
||||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
|
||||||
return &labelError{s, "V2"}
|
|
||||||
}
|
|
||||||
if s[0] == '-' || s[len(s)-1] == '-' {
|
|
||||||
return &labelError{s, "V3"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !p.checkJoiners {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
trie := p.trie // p.checkJoiners is only set if trie is set.
|
|
||||||
// TODO: merge the use of this in the trie.
|
|
||||||
v, sz := trie.lookupString(s)
|
|
||||||
x := info(v)
|
|
||||||
if x.isModifier() {
|
|
||||||
return &labelError{s, "V5"}
|
|
||||||
}
|
|
||||||
// Quickly return in the absence of zero-width (non) joiners.
|
|
||||||
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
st := stateStart
|
|
||||||
for i := 0; ; {
|
|
||||||
jt := x.joinType()
|
|
||||||
if s[i:i+sz] == zwj {
|
|
||||||
jt = joinZWJ
|
|
||||||
} else if s[i:i+sz] == zwnj {
|
|
||||||
jt = joinZWNJ
|
|
||||||
}
|
|
||||||
st = joinStates[st][jt]
|
|
||||||
if x.isViramaModifier() {
|
|
||||||
st = joinStates[st][joinVirama]
|
|
||||||
}
|
|
||||||
if i += sz; i == len(s) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
v, sz = trie.lookupString(s[i:])
|
|
||||||
x = info(v)
|
|
||||||
}
|
|
||||||
if st == stateFAIL || st == stateAfter {
|
|
||||||
return &labelError{s, "C"}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ascii(s string) bool {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if s[i] >= utf8.RuneSelf {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2021 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !go1.18
|
|
||||||
|
|
||||||
package idna
|
|
||||||
|
|
||||||
const transitionalLookup = true
|
|
||||||
+4
-1
@@ -28,7 +28,7 @@ const (
|
|||||||
tmin int32 = 1
|
tmin int32 = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func punyError(s string) error { return &labelError{s, "A3"} }
|
func punyError(s string) error { return &labelError{s, code16("A3", "P4")} }
|
||||||
|
|
||||||
// decode decodes a string as specified in section 6.2.
|
// decode decodes a string as specified in section 6.2.
|
||||||
func decode(encoded string) (string, error) {
|
func decode(encoded string) (string, error) {
|
||||||
@@ -108,6 +108,9 @@ func encode(prefix, s string) (string, error) {
|
|||||||
delta, n, bias := int32(0), initialN, initialBias
|
delta, n, bias := int32(0), initialN, initialBias
|
||||||
b, remaining := int32(0), int32(0)
|
b, remaining := int32(0), int32(0)
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
|
if unicode16 && r == 0xfffd {
|
||||||
|
return s, &labelError{s, "A3"}
|
||||||
|
}
|
||||||
if r < 0x80 {
|
if r < 0x80 {
|
||||||
b++
|
b++
|
||||||
output = append(output, byte(r))
|
output = append(output, byte(r))
|
||||||
|
|||||||
-4559
File diff suppressed because it is too large
Load Diff
-4653
File diff suppressed because it is too large
Load Diff
-4733
File diff suppressed because it is too large
Load Diff
-4959
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||||
|
|
||||||
//go:build go1.21
|
//go:build !go1.27
|
||||||
|
|
||||||
package idna
|
package idna
|
||||||
|
|
||||||
|
|||||||
+5302
File diff suppressed because it is too large
Load Diff
-4486
File diff suppressed because it is too large
Load Diff
-30
@@ -1,30 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !go1.16
|
|
||||||
|
|
||||||
package idna
|
|
||||||
|
|
||||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
|
||||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
|
||||||
func (c info) appendMapping(b []byte, s string) []byte {
|
|
||||||
index := int(c >> indexShift)
|
|
||||||
if c&xorBit == 0 {
|
|
||||||
s := mappings[index:]
|
|
||||||
return append(b, s[1:s[0]+1]...)
|
|
||||||
}
|
|
||||||
b = append(b, s...)
|
|
||||||
if c&inlineXOR == inlineXOR {
|
|
||||||
// TODO: support and handle two-byte inline masks
|
|
||||||
b[len(b)-1] ^= byte(index)
|
|
||||||
} else {
|
|
||||||
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
|
||||||
index++
|
|
||||||
b[p] ^= xorData[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build go1.16
|
|
||||||
|
|
||||||
package idna
|
|
||||||
|
|
||||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
|
||||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
|
||||||
func (c info) appendMapping(b []byte, s string) []byte {
|
|
||||||
index := int(c >> indexShift)
|
|
||||||
if c&xorBit == 0 {
|
|
||||||
p := index
|
|
||||||
return append(b, mappings[mappingIndex[p]:mappingIndex[p+1]]...)
|
|
||||||
}
|
|
||||||
b = append(b, s...)
|
|
||||||
if c&inlineXOR == inlineXOR {
|
|
||||||
// TODO: support and handle two-byte inline masks
|
|
||||||
b[len(b)-1] ^= byte(index)
|
|
||||||
} else {
|
|
||||||
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
|
||||||
index++
|
|
||||||
b[p] ^= xorData[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
+8
@@ -448,6 +448,14 @@ func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
|
|||||||
url_ = &url.URL{Host: rp.Authority}
|
url_ = &url.URL{Host: rp.Authority}
|
||||||
requestURI = rp.Authority // mimic HTTP/1 server behavior
|
requestURI = rp.Authority // mimic HTTP/1 server behavior
|
||||||
} else {
|
} else {
|
||||||
|
// "[The :path] pseudo-header field MUST NOT be empty [...]"
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.4.2
|
||||||
|
if rp.Path == "" || (rp.Path[0] != '/' && rp.Path != "*") {
|
||||||
|
return ServerRequestResult{
|
||||||
|
InvalidReason: "bad_path",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
url_, err = url.ParseRequestURI(rp.Path)
|
url_, err = url.ParseRequestURI(rp.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+37
-25
@@ -6,38 +6,50 @@ package cpu
|
|||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
// parseRelease parses a dot-separated version number. It follows the semver
|
// parseRelease parses a dot-separated version number from the prefix
|
||||||
// syntax, but allows the minor and patch versions to be elided.
|
// of rel. It returns ok=true only if at least the major and minor
|
||||||
|
// components were successfully parsed; the patch component is
|
||||||
|
// best-effort. Trailing vendor or build suffixes such as
|
||||||
|
// "-generic", "+", "_hi3535", or "-rc1" are ignored.
|
||||||
//
|
//
|
||||||
// This is a copy of the Go runtime's parseRelease from
|
// This is a copy of the Go runtime's parseRelease from
|
||||||
// https://golang.org/cl/209597.
|
// https://golang.org/cl/209597, updated in https://golang.org/cl/781800.
|
||||||
func parseRelease(rel string) (major, minor, patch int, ok bool) {
|
func parseRelease(rel string) (major, minor, patch int, ok bool) {
|
||||||
// Strip anything after a dash or plus.
|
// next consumes a run of decimal digits from the front of rel,
|
||||||
for i := range len(rel) {
|
// returning the parsed value. If the digits are followed by a
|
||||||
if rel[i] == '-' || rel[i] == '+' {
|
// '.', it is consumed and more is set so the caller knows to
|
||||||
rel = rel[:i]
|
// parse another component; otherwise scanning terminates and
|
||||||
break
|
// the rest of rel is discarded.
|
||||||
|
next := func() (n int, more, ok bool) {
|
||||||
|
i := 0
|
||||||
|
for i < len(rel) && rel[i] >= '0' && rel[i] <= '9' {
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return 0, false, false
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(rel[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, false
|
||||||
|
}
|
||||||
|
if i < len(rel) && rel[i] == '.' {
|
||||||
|
rel = rel[i+1:]
|
||||||
|
return n, true, true
|
||||||
|
}
|
||||||
|
rel = ""
|
||||||
|
return n, false, true
|
||||||
}
|
}
|
||||||
|
|
||||||
next := func() (int, bool) {
|
var more bool
|
||||||
for i := range len(rel) {
|
if major, more, ok = next(); !ok || !more {
|
||||||
if rel[i] == '.' {
|
return 0, 0, 0, false
|
||||||
ver, err := strconv.Atoi(rel[:i])
|
|
||||||
rel = rel[i+1:]
|
|
||||||
return ver, err == nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ver, err := strconv.Atoi(rel)
|
|
||||||
rel = ""
|
|
||||||
return ver, err == nil
|
|
||||||
}
|
}
|
||||||
if major, ok = next(); !ok || rel == "" {
|
if minor, more, ok = next(); !ok {
|
||||||
return
|
return 0, 0, 0, false
|
||||||
}
|
}
|
||||||
if minor, ok = next(); !ok || rel == "" {
|
if !more {
|
||||||
return
|
return major, minor, 0, true
|
||||||
}
|
}
|
||||||
patch, ok = next()
|
patch, _, _ = next()
|
||||||
return
|
return major, minor, patch, true
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1874,6 +1874,7 @@ func Dup2(oldfd, newfd int) error {
|
|||||||
//sys Dup3(oldfd int, newfd int, flags int) (err error)
|
//sys Dup3(oldfd int, newfd int, flags int) (err error)
|
||||||
//sysnb EpollCreate1(flag int) (fd int, err error)
|
//sysnb EpollCreate1(flag int) (fd int, err error)
|
||||||
//sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
|
//sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
|
||||||
|
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
||||||
//sys Eventfd(initval uint, flags int) (fd int, err error) = SYS_EVENTFD2
|
//sys Eventfd(initval uint, flags int) (fd int, err error) = SYS_EVENTFD2
|
||||||
//sys Exit(code int) = SYS_EXIT_GROUP
|
//sys Exit(code int) = SYS_EXIT_GROUP
|
||||||
//sys Fallocate(fd int, mode uint32, off int64, len int64) (err error)
|
//sys Fallocate(fd int, mode uint32, off int64, len int64) (err error)
|
||||||
|
|||||||
-1
@@ -20,7 +20,6 @@ func setTimeval(sec, usec int64) Timeval {
|
|||||||
|
|
||||||
// 64-bit file system and 32-bit uid calls
|
// 64-bit file system and 32-bit uid calls
|
||||||
// (386 default is 32-bit file system and 16-bit uid).
|
// (386 default is 32-bit file system and 16-bit uid).
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64_64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64_64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
|
|||||||
-1
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
-1
@@ -44,7 +44,6 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
|||||||
|
|
||||||
// 64-bit file system and 32-bit uid calls
|
// 64-bit file system and 32-bit uid calls
|
||||||
// (16-bit uid calls are not always supported in newer kernels)
|
// (16-bit uid calls are not always supported in newer kernels)
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
|||||||
-1
@@ -8,7 +8,6 @@ package unix
|
|||||||
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
-1
@@ -8,7 +8,6 @@ package unix
|
|||||||
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
||||||
|
|||||||
-1
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
||||||
|
|||||||
-1
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Ftruncate(fd int, length int64) (err error) = SYS_FTRUNCATE64
|
//sys Ftruncate(fd int, length int64) (err error) = SYS_FTRUNCATE64
|
||||||
|
|||||||
-1
@@ -11,7 +11,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
|||||||
-1
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
-1
@@ -8,7 +8,6 @@ package unix
|
|||||||
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
-1
@@ -10,7 +10,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
-1
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
|
||||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||||
|
|||||||
+7
-1
@@ -1359,6 +1359,7 @@ const (
|
|||||||
FAN_UNLIMITED_MARKS = 0x20
|
FAN_UNLIMITED_MARKS = 0x20
|
||||||
FAN_UNLIMITED_QUEUE = 0x10
|
FAN_UNLIMITED_QUEUE = 0x10
|
||||||
FD_CLOEXEC = 0x1
|
FD_CLOEXEC = 0x1
|
||||||
|
FD_PIDFS_ROOT = -0x2712
|
||||||
FD_SETSIZE = 0x400
|
FD_SETSIZE = 0x400
|
||||||
FF0 = 0x0
|
FF0 = 0x0
|
||||||
FIB_RULE_DEV_DETACHED = 0x8
|
FIB_RULE_DEV_DETACHED = 0x8
|
||||||
@@ -1970,6 +1971,8 @@ const (
|
|||||||
MADV_DONTNEED = 0x4
|
MADV_DONTNEED = 0x4
|
||||||
MADV_DONTNEED_LOCKED = 0x18
|
MADV_DONTNEED_LOCKED = 0x18
|
||||||
MADV_FREE = 0x8
|
MADV_FREE = 0x8
|
||||||
|
MADV_GUARD_INSTALL = 0x66
|
||||||
|
MADV_GUARD_REMOVE = 0x67
|
||||||
MADV_HUGEPAGE = 0xe
|
MADV_HUGEPAGE = 0xe
|
||||||
MADV_HWPOISON = 0x64
|
MADV_HWPOISON = 0x64
|
||||||
MADV_KEEPONFORK = 0x13
|
MADV_KEEPONFORK = 0x13
|
||||||
@@ -2114,7 +2117,7 @@ const (
|
|||||||
MS_NOSEC = 0x10000000
|
MS_NOSEC = 0x10000000
|
||||||
MS_NOSUID = 0x2
|
MS_NOSUID = 0x2
|
||||||
MS_NOSYMFOLLOW = 0x100
|
MS_NOSYMFOLLOW = 0x100
|
||||||
MS_NOUSER = -0x80000000
|
MS_NOUSER = 0x80000000
|
||||||
MS_POSIXACL = 0x10000
|
MS_POSIXACL = 0x10000
|
||||||
MS_PRIVATE = 0x40000
|
MS_PRIVATE = 0x40000
|
||||||
MS_RDONLY = 0x1
|
MS_RDONLY = 0x1
|
||||||
@@ -3786,6 +3789,9 @@ const (
|
|||||||
TCPOPT_TIMESTAMP = 0x8
|
TCPOPT_TIMESTAMP = 0x8
|
||||||
TCPOPT_TSTAMP_HDR = 0x101080a
|
TCPOPT_TSTAMP_HDR = 0x101080a
|
||||||
TCPOPT_WINDOW = 0x3
|
TCPOPT_WINDOW = 0x3
|
||||||
|
TCP_AO_KEYF_EXCLUDE_OPT = 0x2
|
||||||
|
TCP_AO_KEYF_IFINDEX = 0x1
|
||||||
|
TCP_AO_MAXKEYLEN = 0x50
|
||||||
TCP_CC_INFO = 0x1a
|
TCP_CC_INFO = 0x1a
|
||||||
TCP_CM_INQ = 0x24
|
TCP_CM_INQ = 0x24
|
||||||
TCP_CONGESTION = 0xd
|
TCP_CONGESTION = 0xd
|
||||||
|
|||||||
+17
@@ -700,6 +700,23 @@ func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(events) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&events[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func Eventfd(initval uint, flags int) (fd int, err error) {
|
func Eventfd(initval uint, flags int) (fd int, err error) {
|
||||||
r0, _, e1 := Syscall(SYS_EVENTFD2, uintptr(initval), uintptr(flags), 0)
|
r0, _, e1 := Syscall(SYS_EVENTFD2, uintptr(initval), uintptr(flags), 0)
|
||||||
fd = int(r0)
|
fd = int(r0)
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64_64, uintptr(fd), uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice))
|
_, _, e1 := Syscall6(SYS_FADVISE64_64, uintptr(fd), uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice))
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -213,23 +213,6 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fchown(fd int, uid int, gid int) (err error) {
|
func Fchown(fd int, uid int, gid int) (err error) {
|
||||||
_, _, e1 := Syscall(SYS_FCHOWN32, uintptr(fd), uintptr(uid), uintptr(gid))
|
_, _, e1 := Syscall(SYS_FCHOWN32, uintptr(fd), uintptr(uid), uintptr(gid))
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset>>32), uintptr(offset), uintptr(length>>32), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset>>32), uintptr(offset), uintptr(length>>32), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fchown(fd int, uid int, gid int) (err error) {
|
func Fchown(fd int, uid int, gid int) (err error) {
|
||||||
_, _, e1 := Syscall(SYS_FCHOWN, uintptr(fd), uintptr(uid), uintptr(gid))
|
_, _, e1 := Syscall(SYS_FCHOWN, uintptr(fd), uintptr(uid), uintptr(gid))
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
|||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
|
||||||
var _p0 unsafe.Pointer
|
|
||||||
if len(events) > 0 {
|
|
||||||
_p0 = unsafe.Pointer(&events[0])
|
|
||||||
} else {
|
|
||||||
_p0 = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
|
||||||
n = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
|
||||||
|
|
||||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||||
if e1 != 0 {
|
if e1 != 0 {
|
||||||
|
|||||||
+36
@@ -1109,17 +1109,53 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
|
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
|
||||||
|
//
|
||||||
|
// Go pointers stored in a TrusteeValue must be pinned using [runtime.Pinner]
|
||||||
|
// for the lifetime of the TrusteeValue.
|
||||||
type TrusteeValue uintptr
|
type TrusteeValue uintptr
|
||||||
|
|
||||||
|
// TrusteeValueFromString is unsafe and should not be used.
|
||||||
|
//
|
||||||
|
// It returns a uintptr containing a reference to newly-allocated memory
|
||||||
|
// which will be freed by the garbage collector.
|
||||||
|
// There is no way for the caller to safely reference this memory.
|
||||||
|
//
|
||||||
|
// To create a [TrusteeValue] from a string, use:
|
||||||
|
//
|
||||||
|
// p, err := windows.UTF16PtrFromString(s)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Pin the string for as long as it is used.
|
||||||
|
// var pinner runtime.Pinner
|
||||||
|
// pinner.Pin(p)
|
||||||
|
// defer pinner.Unpin()
|
||||||
|
//
|
||||||
|
// tv := TrusteeValue(unsafe.Pointer(p))
|
||||||
|
//
|
||||||
|
// Deprecated: TrusteeValueFromString is unsafe and should not be used.
|
||||||
func TrusteeValueFromString(str string) TrusteeValue {
|
func TrusteeValueFromString(str string) TrusteeValue {
|
||||||
return TrusteeValue(unsafe.Pointer(StringToUTF16Ptr(str)))
|
return TrusteeValue(unsafe.Pointer(StringToUTF16Ptr(str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrusteeValueFromSID returns a [TrusteeValue] referencing sid.
|
||||||
|
//
|
||||||
|
// The caller must pin sid using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||||
func TrusteeValueFromSID(sid *SID) TrusteeValue {
|
func TrusteeValueFromSID(sid *SID) TrusteeValue {
|
||||||
return TrusteeValue(unsafe.Pointer(sid))
|
return TrusteeValue(unsafe.Pointer(sid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrusteeValueFromObjectsAndSid returns a [TrusteeValue] referencing objectsAndSid.
|
||||||
|
//
|
||||||
|
// The caller must pin objectsAndSid using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||||
func TrusteeValueFromObjectsAndSid(objectsAndSid *OBJECTS_AND_SID) TrusteeValue {
|
func TrusteeValueFromObjectsAndSid(objectsAndSid *OBJECTS_AND_SID) TrusteeValue {
|
||||||
return TrusteeValue(unsafe.Pointer(objectsAndSid))
|
return TrusteeValue(unsafe.Pointer(objectsAndSid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrusteeValueFromObjectsAndName returns a [TrusteeValue] referencing objectsAndName.
|
||||||
|
//
|
||||||
|
// The caller must pin objectsAndName using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||||
func TrusteeValueFromObjectsAndName(objectsAndName *OBJECTS_AND_NAME) TrusteeValue {
|
func TrusteeValueFromObjectsAndName(objectsAndName *OBJECTS_AND_NAME) TrusteeValue {
|
||||||
return TrusteeValue(unsafe.Pointer(objectsAndName))
|
return TrusteeValue(unsafe.Pointer(objectsAndName))
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -1728,11 +1728,15 @@ func (s *NTUnicodeString) String() string {
|
|||||||
// the more common *uint16 string type.
|
// the more common *uint16 string type.
|
||||||
func NewNTString(s string) (*NTString, error) {
|
func NewNTString(s string) (*NTString, error) {
|
||||||
var nts NTString
|
var nts NTString
|
||||||
s8, err := BytePtrFromString(s)
|
s8, err := ByteSliceFromString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
RtlInitString(&nts, s8)
|
// The source string plus its terminating NUL must fit within MAX_USHORT.
|
||||||
|
if len(s8) > MAX_USHORT {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
RtlInitString(&nts, &s8[0])
|
||||||
return &nts, nil
|
return &nts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -169,6 +169,7 @@ const (
|
|||||||
FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192
|
FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192
|
||||||
FORMAT_MESSAGE_MAX_WIDTH_MASK = 255
|
FORMAT_MESSAGE_MAX_WIDTH_MASK = 255
|
||||||
|
|
||||||
|
MAX_USHORT = 0xffff
|
||||||
MAX_PATH = 260
|
MAX_PATH = 260
|
||||||
MAX_LONG_PATH = 32768
|
MAX_LONG_PATH = 32768
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -249,7 +249,7 @@ func upper(c *context) bool {
|
|||||||
return c.copy()
|
return c.copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUpper writes the isUppercase version of the current rune to dst.
|
// isUpper reports whether the current rune is in upper case.
|
||||||
func isUpper(c *context) bool {
|
func isUpper(c *context) bool {
|
||||||
ct := c.caseType()
|
ct := c.caseType()
|
||||||
if c.info&hasMappingMask == 0 || ct == cUpper {
|
if c.info&hasMappingMask == 0 || ct == cUpper {
|
||||||
|
|||||||
+2
-2
@@ -774,7 +774,7 @@ func nlTitle(c *context) bool {
|
|||||||
// From CLDR:
|
// From CLDR:
|
||||||
// # Special titlecasing for Dutch initial "ij".
|
// # Special titlecasing for Dutch initial "ij".
|
||||||
// ::Any-Title();
|
// ::Any-Title();
|
||||||
// # Fix up Ij at the beginning of a "word" (per Any-Title, notUAX #29)
|
// # Fix up Ij at the beginning of a "word" (per Any-Title, not UAX #29)
|
||||||
// [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ;
|
// [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ;
|
||||||
if c.src[c.pSrc] != 'I' && c.src[c.pSrc] != 'i' {
|
if c.src[c.pSrc] != 'I' && c.src[c.pSrc] != 'i' {
|
||||||
return title(c)
|
return title(c)
|
||||||
@@ -794,7 +794,7 @@ func nlTitleSpan(c *context) bool {
|
|||||||
// From CLDR:
|
// From CLDR:
|
||||||
// # Special titlecasing for Dutch initial "ij".
|
// # Special titlecasing for Dutch initial "ij".
|
||||||
// ::Any-Title();
|
// ::Any-Title();
|
||||||
// # Fix up Ij at the beginning of a "word" (per Any-Title, notUAX #29)
|
// # Fix up Ij at the beginning of a "word" (per Any-Title, not UAX #29)
|
||||||
// [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ;
|
// [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ;
|
||||||
if c.src[c.pSrc] != 'I' {
|
if c.src[c.pSrc] != 'I' {
|
||||||
return isTitle(c)
|
return isTitle(c)
|
||||||
|
|||||||
+8
-1
@@ -121,8 +121,12 @@ func (p Properties) BoundaryAfter() bool {
|
|||||||
//
|
//
|
||||||
// When all 6 bits are zero, the character is inert, meaning it is never
|
// When all 6 bits are zero, the character is inert, meaning it is never
|
||||||
// influenced by normalization.
|
// influenced by normalization.
|
||||||
|
//
|
||||||
|
// We set flags to 0x80 (high bit 7 unused in quick check data) to indicate an invalid rune.
|
||||||
type qcInfo uint8
|
type qcInfo uint8
|
||||||
|
|
||||||
|
func (p Properties) isInvalid() bool { return p.flags == 0x80 }
|
||||||
|
|
||||||
func (p Properties) isYesC() bool { return p.flags&0x10 == 0 }
|
func (p Properties) isYesC() bool { return p.flags&0x10 == 0 }
|
||||||
func (p Properties) isYesD() bool { return p.flags&0x4 == 0 }
|
func (p Properties) isYesD() bool { return p.flags&0x4 == 0 }
|
||||||
|
|
||||||
@@ -247,6 +251,9 @@ func (f Form) PropertiesString(s string) Properties {
|
|||||||
// to a Properties. See the comment at the top of the file
|
// to a Properties. See the comment at the top of the file
|
||||||
// for more information on the format.
|
// for more information on the format.
|
||||||
func compInfo(v uint16, sz int) Properties {
|
func compInfo(v uint16, sz int) Properties {
|
||||||
|
if sz == 0 {
|
||||||
|
return Properties{flags: 0x80, size: 1}
|
||||||
|
}
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
return Properties{size: uint8(sz)}
|
return Properties{size: uint8(sz)}
|
||||||
} else if v >= 0x8000 {
|
} else if v >= 0x8000 {
|
||||||
@@ -254,7 +261,7 @@ func compInfo(v uint16, sz int) Properties {
|
|||||||
size: uint8(sz),
|
size: uint8(sz),
|
||||||
ccc: uint8(v),
|
ccc: uint8(v),
|
||||||
tccc: uint8(v),
|
tccc: uint8(v),
|
||||||
flags: qcInfo(v >> 8),
|
flags: qcInfo(v>>8) & 0x3f,
|
||||||
}
|
}
|
||||||
if p.ccc > 0 || p.combinesBackward() {
|
if p.ccc > 0 || p.combinesBackward() {
|
||||||
p.nLead = uint8(p.flags & 0x3)
|
p.nLead = uint8(p.flags & 0x3)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user