mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-06-23 04:10:20 +00:00
fix: Update golang.org/x/net to v0.54.0
Check / check (1.22.x, ubuntu-latest) (push) Failing after 5m15s
Semgrep config / semgrep/ci (push) Failing after 1m19s
Check / check (1.22.x, macos-latest) (push) Has been cancelled
Check / check (1.22.x, windows-latest) (push) Has been cancelled
Check / check (1.22.x, ubuntu-latest) (push) Failing after 5m15s
Semgrep config / semgrep/ci (push) Failing after 1m19s
Check / check (1.22.x, macos-latest) (push) Has been cancelled
Check / check (1.22.x, windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -36,11 +36,11 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.2.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/mock v0.5.1
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/net v0.54.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/term v0.43.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -91,10 +91,10 @@ require (
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
|
||||
@@ -245,16 +245,16 @@ golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -269,20 +269,20 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+13
-8
@@ -11,6 +11,7 @@
|
||||
package hkdf
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -24,15 +25,19 @@ import (
|
||||
// Expand invocations and different context values. Most common scenarios,
|
||||
// including the generation of multiple keys, should use New instead.
|
||||
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
|
||||
if salt == nil {
|
||||
salt = make([]byte, hash().Size())
|
||||
// Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
|
||||
// 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)
|
||||
extractor.Write(secret)
|
||||
return extractor.Sum(nil)
|
||||
return out
|
||||
}
|
||||
|
||||
type hkdf struct {
|
||||
type hkdfReader struct {
|
||||
expander hash.Hash
|
||||
size int
|
||||
|
||||
@@ -43,7 +48,7 @@ type hkdf struct {
|
||||
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
|
||||
need := len(p)
|
||||
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.
|
||||
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
|
||||
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,
|
||||
|
||||
+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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
// Transport code's client connection pooling.
|
||||
|
||||
package http2
|
||||
@@ -14,18 +16,6 @@ import (
|
||||
"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
|
||||
// implementations which can close their idle connections.
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
+3
-10
@@ -6,7 +6,6 @@ package hpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// headerFieldTable implements a list of HeaderFields.
|
||||
@@ -55,16 +54,10 @@ func (t *headerFieldTable) len() int {
|
||||
|
||||
// addEntry adds a new entry.
|
||||
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
|
||||
t.byName[f2.Name] = id
|
||||
t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
|
||||
t.ents = append(t.ents, f2)
|
||||
t.byName[f.Name] = id
|
||||
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
||||
t.ents = append(t.ents, f)
|
||||
}
|
||||
|
||||
// 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
|
||||
// name (key). See httpguts.ValidHeaderName for the base rules.
|
||||
// name (key). See httpguts.ValidHeaderFieldName for the base rules.
|
||||
//
|
||||
// Further, http2 says:
|
||||
//
|
||||
|
||||
+9
-184
@@ -2,6 +2,8 @@
|
||||
// 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)
|
||||
|
||||
// TODO: turn off the serve goroutine when idle, so
|
||||
// an idle conn only has the readFrames goroutine active. (which could
|
||||
// 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)
|
||||
)
|
||||
|
||||
// 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 {
|
||||
mu sync.Mutex
|
||||
activeConns map[*serverConn]struct{}
|
||||
@@ -187,6 +97,9 @@ type serverInternalState struct {
|
||||
// Pool of error channels. This is per-Server rather than global
|
||||
// because channels can't be reused across synctest bubbles.
|
||||
errChanPool sync.Pool
|
||||
|
||||
// Used in tests.
|
||||
testNewConn func(*serverConn)
|
||||
}
|
||||
|
||||
func (s *serverInternalState) registerConn(sc *serverConn) {
|
||||
@@ -239,12 +152,7 @@ func (s *serverInternalState) putErrChan(ch chan error) {
|
||||
s.errChanPool.Put(ch)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func configureServer(s *http.Server, conf *Server) error {
|
||||
if s == nil {
|
||||
panic("nil *http.Server")
|
||||
}
|
||||
@@ -349,83 +257,6 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||
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)) {
|
||||
baseCtx, cancel := serverConnBaseContext(c, opts)
|
||||
defer cancel()
|
||||
@@ -461,6 +292,9 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
|
||||
if newf != nil {
|
||||
newf(sc)
|
||||
}
|
||||
if s.state != nil && s.state.testNewConn != nil {
|
||||
s.state.testNewConn(sc)
|
||||
}
|
||||
|
||||
s.state.registerConn(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)
|
||||
}
|
||||
|
||||
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) {
|
||||
sc.vlogf("http2: server rejecting conn: %v, %s", err, debug)
|
||||
// ignoring errors. hanging up anyway.
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// 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"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"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
|
||||
}
|
||||
}
|
||||
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.
|
||||
}
|
||||
+28
-391
@@ -2,6 +2,8 @@
|
||||
// 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 code.
|
||||
|
||||
package http2
|
||||
@@ -21,20 +23,17 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"math/bits"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/idna"
|
||||
"golang.org/x/net/internal/httpcommon"
|
||||
)
|
||||
|
||||
@@ -60,123 +59,7 @@ const (
|
||||
defaultMaxConcurrentStreams = 1000
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
type transportInternal struct {
|
||||
// t1, if non-nil, is the standard library Transport using
|
||||
// this transport. Its settings are used (but not its
|
||||
// RoundTrip method, etc).
|
||||
@@ -217,27 +100,18 @@ func (t *Transport) disableCompression() bool {
|
||||
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
_, err := ConfigureTransports(t1)
|
||||
func configureTransport(t1 *http.Transport) error {
|
||||
_, err := configureTransports(t1)
|
||||
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) {
|
||||
connPool := new(clientConnPool)
|
||||
t2 := &Transport{
|
||||
ConnPool: noDialClientConnPool{connPool},
|
||||
t1: t1,
|
||||
transportInternal: transportInternal{
|
||||
t1: t1,
|
||||
},
|
||||
}
|
||||
connPool.t = t2
|
||||
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||
@@ -546,47 +420,7 @@ func isNoCachedConnError(err error) bool {
|
||||
|
||||
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) {
|
||||
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
switch req.URL.Scheme {
|
||||
case "https":
|
||||
// Always okay.
|
||||
@@ -597,126 +431,15 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
||||
default:
|
||||
return nil, errors.New("http2: unsupported scheme")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return t.roundTripViaPool(req, opt, t.connPool())
|
||||
}
|
||||
|
||||
// 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() {
|
||||
func (t *Transport) closeIdleConnections() {
|
||||
if cp, ok := t.connPool().(clientConnPoolIdleCloser); ok {
|
||||
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) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
@@ -743,27 +466,6 @@ func (t *Transport) newTLSConfig(host string) *tls.Config {
|
||||
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
|
||||
// soon as possible after handling the first request.
|
||||
func (t *Transport) disableKeepAlives() bool {
|
||||
@@ -777,7 +479,7 @@ func (t *Transport) expectContinueTimeout() time.Duration {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -890,8 +592,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()
|
||||
defer cc.mu.Unlock()
|
||||
cc.doNotReuse = true
|
||||
@@ -932,21 +633,13 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (cc *ClientConn) canTakeNewRequest() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.canTakeNewRequestLocked()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (cc *ClientConn) reserveNewRequest() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if st := cc.idleStateLocked(); !st.canTakeNewRequest {
|
||||
@@ -956,41 +649,7 @@ func (cc *ClientConn) ReserveNewRequest() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// State returns a snapshot of cc's state.
|
||||
func (cc *ClientConn) State() ClientConnState {
|
||||
func (cc *ClientConn) state() ClientConnState {
|
||||
cc.wmu.Lock()
|
||||
maxConcurrent := cc.maxConcurrentStreams
|
||||
if !cc.seenSettings {
|
||||
@@ -1161,6 +820,12 @@ func (cc *ClientConn) closeIfIdle() {
|
||||
cc.closeConn()
|
||||
}
|
||||
|
||||
func (cc *ClientConn) stopIdleTimer() {
|
||||
if cc.idleTimer != nil {
|
||||
cc.idleTimer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
@@ -1169,8 +834,7 @@ func (cc *ClientConn) isDoNotReuseAndIdle() bool {
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -1244,10 +908,7 @@ func (cc *ClientConn) closeForError(err error) {
|
||||
cc.closeConn()
|
||||
}
|
||||
|
||||
// Close closes the client connection immediately.
|
||||
//
|
||||
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
||||
func (cc *ClientConn) Close() error {
|
||||
func (cc *ClientConn) close() error {
|
||||
cc.closeForError(errClientConnForceClosed)
|
||||
return nil
|
||||
}
|
||||
@@ -1301,11 +962,11 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.roundTrip(req, nil)
|
||||
func (cc *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
|
||||
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()
|
||||
cs := &clientStream{
|
||||
cc: cc,
|
||||
@@ -2978,7 +2639,7 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
|
||||
}
|
||||
|
||||
// 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{})
|
||||
// Generate a random payload
|
||||
var p [8]byte
|
||||
@@ -3092,16 +2753,6 @@ func (cc *ClientConn) vlogf(format string, args ...interface{}) {
|
||||
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{}
|
||||
|
||||
type noBodyReader struct{}
|
||||
@@ -3417,17 +3068,3 @@ func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.M
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
// 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
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
// 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 {
|
||||
// ConfigureTransport is a no-op: The http.Transport already supports HTTP/2.
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !(go1.27 && !http2legacy)
|
||||
|
||||
package http2
|
||||
|
||||
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.
|
||||
//
|
||||
// Deprecated: User-provided write schedulers are deprecated.
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// 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
|
||||
}
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// 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)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// 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)
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// 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)
|
||||
|
||||
package http2
|
||||
|
||||
import "math"
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
// 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)
|
||||
|
||||
package http2
|
||||
|
||||
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
|
||||
// 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.
|
||||
@@ -20,6 +18,7 @@ package idna // import "golang.org/x/net/idna"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/secure/bidirule"
|
||||
@@ -27,6 +26,8 @@ import (
|
||||
"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
|
||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
||||
// 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
|
||||
// labels. Most web browsers do not have this option set, since labels such as
|
||||
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
||||
@@ -263,6 +269,10 @@ func (p *Profile) String() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Transitional processing is disabled by default as of Go 1.18.
|
||||
// https://golang.org/issue/47510
|
||||
const transitionalLookup = false
|
||||
|
||||
var (
|
||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
||||
// of validation.
|
||||
@@ -324,15 +334,30 @@ 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)
|
||||
type runeError struct {
|
||||
r rune
|
||||
code_ string
|
||||
}
|
||||
|
||||
// process implements the algorithm described in section 4 of UTS #46,
|
||||
// see https://www.unicode.org/reports/tr46.
|
||||
func (e runeError) code() string { return e.code_ }
|
||||
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) {
|
||||
var err error
|
||||
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.
|
||||
// It seems like we should only create this error on ToASCII, but the
|
||||
// UTS 46 conformance tests suggests we should always check this.
|
||||
labelCode := "X4_2"
|
||||
if !unicode16 || toASCII {
|
||||
labelCode = "A4"
|
||||
}
|
||||
if err == nil && p.verifyDNSLength && s == "" {
|
||||
err = &labelError{s, "A4"}
|
||||
err = labelError{s, labelCode}
|
||||
}
|
||||
labels := labelIter{orig: s}
|
||||
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
|
||||
// label if it is empty.
|
||||
if err == nil && p.verifyDNSLength {
|
||||
err = &labelError{s, "A4"}
|
||||
err = labelError{s, labelCode}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(label, acePrefix) {
|
||||
u, err2 := decode(label[len(acePrefix):])
|
||||
enc := label[len(acePrefix):]
|
||||
u, err2 := decode(enc)
|
||||
if err2 != nil {
|
||||
if err == nil {
|
||||
err = err2
|
||||
@@ -370,6 +400,9 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||
// Spec says keep the old label.
|
||||
continue
|
||||
}
|
||||
if unicode16 && err == nil && len(u) > 0 && isASCII(u) {
|
||||
err = punyError(enc)
|
||||
}
|
||||
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
||||
labels.set(u)
|
||||
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
|
||||
// spec, but that currently does not have any effect. Use the
|
||||
// original profile to preserve options.
|
||||
err = p.validateLabel(u)
|
||||
err = p.validateLabel(u, labelCode)
|
||||
}
|
||||
} else if err == nil {
|
||||
err = p.validateLabel(label)
|
||||
err = p.validateLabel(label, labelCode)
|
||||
}
|
||||
}
|
||||
if isBidi && p.bidirule != nil && err == nil {
|
||||
for labels.reset(); !labels.done(); labels.next() {
|
||||
if !p.bidirule(labels.label()) {
|
||||
err = &labelError{s, "B"}
|
||||
err = labelError{s, "B"}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -406,24 +439,36 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||
}
|
||||
n := len(label)
|
||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
|
||||
err = &labelError{label, "A4"}
|
||||
err = labelError{label, labelCode}
|
||||
}
|
||||
}
|
||||
}
|
||||
s = labels.result()
|
||||
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.
|
||||
n := len(s)
|
||||
if n > 0 && s[n-1] == '.' {
|
||||
n--
|
||||
}
|
||||
if len(s) < 1 || n > 253 {
|
||||
err = &labelError{s, "A4"}
|
||||
err = labelError{s, labelCode}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// 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
|
||||
@@ -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) {
|
||||
// TODO: filter need for normalization in loop below.
|
||||
if !norm.NFC.IsNormalString(s) {
|
||||
return s, false, &labelError{s, "V1"}
|
||||
return s, false, labelError{s, "V1"}
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
v, sz := trie.lookupString(s[i:])
|
||||
if sz == 0 {
|
||||
return s, bidi, runeError(utf8.RuneError)
|
||||
return s, bidi, runeError{utf8.RuneError, "P1"}
|
||||
}
|
||||
bidi = bidi || info(v).isBidi(s[i:])
|
||||
// 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
|
||||
// for strict conformance to IDNA2008.
|
||||
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:
|
||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
||||
return s, bidi, runeError(r)
|
||||
return s, bidi, runeError{r, "P1"}
|
||||
}
|
||||
i += sz
|
||||
}
|
||||
@@ -489,7 +537,7 @@ func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
||||
b = append(b, "\ufffd"...)
|
||||
k = len(s)
|
||||
if err == nil {
|
||||
err = runeError(utf8.RuneError)
|
||||
err = runeError{utf8.RuneError, "P1"}
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -502,14 +550,26 @@ func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
||||
case valid:
|
||||
continue
|
||||
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:])
|
||||
err = runeError(r)
|
||||
err = runeError{r, "P1"}
|
||||
}
|
||||
continue
|
||||
case mapped, deviation:
|
||||
case deviation:
|
||||
if unicode16 && !p.transitional {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case mapped:
|
||||
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:
|
||||
b = append(b, s[k:start]...)
|
||||
// drop the rune
|
||||
@@ -600,13 +660,13 @@ const acePrefix = "xn--"
|
||||
|
||||
func (p *Profile) simplify(cat category) category {
|
||||
switch cat {
|
||||
case disallowedSTD3Mapped:
|
||||
case disallowedSTD3Mapped: // only happens for pre-Unicode 16
|
||||
if p.useSTD3Rules {
|
||||
cat = disallowed
|
||||
} else {
|
||||
cat = mapped
|
||||
}
|
||||
case disallowedSTD3Valid:
|
||||
case disallowedSTD3Valid: // only happens for pre-Unicode 16
|
||||
if p.useSTD3Rules {
|
||||
cat = disallowed
|
||||
} else {
|
||||
@@ -625,17 +685,18 @@ func (p *Profile) simplify(cat category) category {
|
||||
|
||||
func validateFromPunycode(p *Profile, s string) error {
|
||||
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
|
||||
// loop.
|
||||
for i := 0; i < len(s); {
|
||||
v, sz := trie.lookupString(s[i:])
|
||||
if sz == 0 {
|
||||
return runeError(utf8.RuneError)
|
||||
return runeError{utf8.RuneError, "P1"}
|
||||
}
|
||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
||||
return &labelError{s, "V6"}
|
||||
cat := info(v).category()
|
||||
if c := p.simplify(cat); c != valid && c != deviation {
|
||||
return labelError{s, code16("V6", "V7")}
|
||||
}
|
||||
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
|
||||
// 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 p.verifyDNSLength {
|
||||
return &labelError{s, "A4"}
|
||||
return labelError{s, labelCode}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if p.checkHyphens {
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
||||
return &labelError{s, "V2"}
|
||||
return labelError{s, "V2"}
|
||||
}
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@@ -729,7 +818,7 @@ func (p *Profile) validateLabel(s string) (err error) {
|
||||
v, sz := trie.lookupString(s)
|
||||
x := info(v)
|
||||
if x.isModifier() {
|
||||
return &labelError{s, "V5"}
|
||||
return labelError{s, code16("V5", "V6")}
|
||||
}
|
||||
// Quickly return in the absence of zero-width (non) joiners.
|
||||
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)
|
||||
}
|
||||
if st == stateFAIL || st == stateAfter {
|
||||
return &labelError{s, "C"}
|
||||
return labelError{s, "C"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -767,3 +857,24 @@ func ascii(s string) bool {
|
||||
}
|
||||
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
|
||||
)
|
||||
|
||||
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.
|
||||
func decode(encoded string) (string, error) {
|
||||
@@ -108,6 +108,9 @@ func encode(prefix, s string) (string, error) {
|
||||
delta, n, bias := int32(0), initialN, initialBias
|
||||
b, remaining := int32(0), int32(0)
|
||||
for _, r := range s {
|
||||
if unicode16 && r == 0xfffd {
|
||||
return s, &labelError{s, "A3"}
|
||||
}
|
||||
if r < 0x80 {
|
||||
b++
|
||||
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.
|
||||
|
||||
//go:build go1.21
|
||||
//go:build !go1.27
|
||||
|
||||
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}
|
||||
requestURI = rp.Authority // mimic HTTP/1 server behavior
|
||||
} 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
|
||||
url_, err = url.ParseRequestURI(rp.Path)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !darwin && !linux && !netbsd && !openbsd && arm64
|
||||
//go:build !darwin && !linux && !netbsd && !openbsd && !windows && arm64
|
||||
|
||||
package cpu
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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 cpu
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -systemdll=false -output zcpu_windows.go cpu_windows.go
|
||||
|
||||
//sys isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) = kernel32.IsProcessorFeaturePresent
|
||||
|
||||
// The processor features to be tested for IsProcessorFeaturePresent, see
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
|
||||
const (
|
||||
_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
|
||||
_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31
|
||||
_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34
|
||||
_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43
|
||||
|
||||
_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44
|
||||
_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE = 45
|
||||
_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE = 46
|
||||
_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE = 47
|
||||
|
||||
_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE = 64
|
||||
_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE = 65
|
||||
)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// 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 cpu
|
||||
|
||||
func doinit() {
|
||||
// set HasASIMD and HasFP to true as per
|
||||
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
|
||||
//
|
||||
// The ARM64 version of Windows always presupposes that it's running on an ARMv8 or later architecture.
|
||||
// Both floating-point and NEON support are presumed to be present in hardware.
|
||||
//
|
||||
ARM64.HasASIMD = true
|
||||
ARM64.HasFP = true
|
||||
|
||||
if isProcessorFeaturePresent(_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasAES = true
|
||||
ARM64.HasPMULL = true
|
||||
ARM64.HasSHA1 = true
|
||||
ARM64.HasSHA2 = true
|
||||
}
|
||||
ARM64.HasSHA3 = isProcessorFeaturePresent(_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasCRC32 = isProcessorFeaturePresent(_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSHA512 = isProcessorFeaturePresent(_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasATOMICS = isProcessorFeaturePresent(_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
|
||||
if isProcessorFeaturePresent(_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasASIMDDP = true
|
||||
ARM64.HasASIMDRDM = true
|
||||
}
|
||||
if isProcessorFeaturePresent(_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
|
||||
ARM64.HasLRCPC = true
|
||||
ARM64.HasSM3 = true
|
||||
}
|
||||
ARM64.HasSVE = isProcessorFeaturePresent(_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasSVE2 = isProcessorFeaturePresent(_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
|
||||
ARM64.HasJSCVT = isProcessorFeaturePresent(_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
procIsProcessorFeaturePresent = modkernel32.NewProc("IsProcessorFeaturePresent")
|
||||
)
|
||||
|
||||
func isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) {
|
||||
r0, _, _ := syscall.SyscallN(procIsProcessorFeaturePresent.Addr(), uintptr(ProcessorFeature))
|
||||
ret = r0 != 0
|
||||
return
|
||||
}
|
||||
+112
-16
@@ -13,11 +13,19 @@ import (
|
||||
|
||||
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
||||
|
||||
// CPUSet represents a CPU affinity mask.
|
||||
// CPUSet represents a bit mask of CPUs, to be used with [SchedGetaffinity], [SchedSetaffinity],
|
||||
// and [SetMemPolicy].
|
||||
//
|
||||
// Note this type can only represent CPU IDs 0 through 1023.
|
||||
// Use [CPUSetDynamic]/[NewCPUSet] instead to avoid this limit.
|
||||
type CPUSet [cpuSetSize]cpuMask
|
||||
|
||||
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
||||
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
|
||||
// CPUSetDynamic represents a bit mask of CPUs, to be used with [SchedGetaffinityDynamic],
|
||||
// [SchedSetaffinityDynamic], and [SetMemPolicyDynamic]. Use [NewCPUSet] to allocate.
|
||||
type CPUSetDynamic []cpuMask
|
||||
|
||||
func schedAffinity(trap uintptr, pid int, size uintptr, ptr unsafe.Pointer) error {
|
||||
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(size), uintptr(ptr))
|
||||
if e != 0 {
|
||||
return errnoErr(e)
|
||||
}
|
||||
@@ -27,13 +35,13 @@ func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
||||
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
func SchedGetaffinity(pid int, set *CPUSet) error {
|
||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set)
|
||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
|
||||
}
|
||||
|
||||
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
func SchedSetaffinity(pid int, set *CPUSet) error {
|
||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set)
|
||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
|
||||
}
|
||||
|
||||
// Zero clears the set s, so that it contains no CPUs.
|
||||
@@ -45,9 +53,7 @@ func (s *CPUSet) Zero() {
|
||||
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
|
||||
// efficient way of resetting the CPU affinity of a process.
|
||||
func (s *CPUSet) Fill() {
|
||||
for i := range s {
|
||||
s[i] = ^cpuMask(0)
|
||||
}
|
||||
cpuMaskFill(s[:])
|
||||
}
|
||||
|
||||
func cpuBitsIndex(cpu int) int {
|
||||
@@ -58,24 +64,27 @@ func cpuBitsMask(cpu int) cpuMask {
|
||||
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
||||
}
|
||||
|
||||
// Set adds cpu to the set s.
|
||||
func (s *CPUSet) Set(cpu int) {
|
||||
func cpuMaskFill(s []cpuMask) {
|
||||
for i := range s {
|
||||
s[i] = ^cpuMask(0)
|
||||
}
|
||||
}
|
||||
|
||||
func cpuMaskSet(s []cpuMask, cpu int) {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
s[i] |= cpuBitsMask(cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes cpu from the set s.
|
||||
func (s *CPUSet) Clear(cpu int) {
|
||||
func cpuMaskClear(s []cpuMask, cpu int) {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
s[i] &^= cpuBitsMask(cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// IsSet reports whether cpu is in the set s.
|
||||
func (s *CPUSet) IsSet(cpu int) bool {
|
||||
func cpuMaskIsSet(s []cpuMask, cpu int) bool {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
return s[i]&cpuBitsMask(cpu) != 0
|
||||
@@ -83,11 +92,98 @@ func (s *CPUSet) IsSet(cpu int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Count returns the number of CPUs in the set s.
|
||||
func (s *CPUSet) Count() int {
|
||||
func cpuMaskCount(s []cpuMask) int {
|
||||
c := 0
|
||||
for _, b := range s {
|
||||
c += bits.OnesCount64(uint64(b))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
|
||||
func (s *CPUSet) Set(cpu int) {
|
||||
cpuMaskSet(s[:], cpu)
|
||||
}
|
||||
|
||||
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
|
||||
func (s *CPUSet) Clear(cpu int) {
|
||||
cpuMaskClear(s[:], cpu)
|
||||
}
|
||||
|
||||
// IsSet reports whether cpu is in the set s.
|
||||
func (s *CPUSet) IsSet(cpu int) bool {
|
||||
return cpuMaskIsSet(s[:], cpu)
|
||||
}
|
||||
|
||||
// Count returns the number of CPUs in the set s.
|
||||
func (s *CPUSet) Count() int {
|
||||
return cpuMaskCount(s[:])
|
||||
}
|
||||
|
||||
// NewCPUSet creates a CPU affinity mask capable of representing CPU IDs
|
||||
// up to maxCPU (exclusive).
|
||||
func NewCPUSet(maxCPU int) CPUSetDynamic {
|
||||
numMasks := (maxCPU + _NCPUBITS - 1) / _NCPUBITS
|
||||
if numMasks == 0 {
|
||||
numMasks = 1
|
||||
}
|
||||
return make(CPUSetDynamic, numMasks)
|
||||
}
|
||||
|
||||
// Zero clears the set s, so that it contains no CPUs.
|
||||
func (s CPUSetDynamic) Zero() {
|
||||
clear(s)
|
||||
}
|
||||
|
||||
// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinityDynamic]
|
||||
// will silently ignore any invalid CPU bits in [CPUSetDynamic] so this is an
|
||||
// efficient way of resetting the CPU affinity of a process.
|
||||
func (s CPUSetDynamic) Fill() {
|
||||
cpuMaskFill(s)
|
||||
}
|
||||
|
||||
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
|
||||
func (s CPUSetDynamic) Set(cpu int) {
|
||||
cpuMaskSet(s, cpu)
|
||||
}
|
||||
|
||||
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
|
||||
func (s CPUSetDynamic) Clear(cpu int) {
|
||||
cpuMaskClear(s, cpu)
|
||||
}
|
||||
|
||||
// IsSet reports whether cpu is in the set s.
|
||||
func (s CPUSetDynamic) IsSet(cpu int) bool {
|
||||
return cpuMaskIsSet(s, cpu)
|
||||
}
|
||||
|
||||
// Count returns the number of CPUs in the set s.
|
||||
func (s CPUSetDynamic) Count() int {
|
||||
return cpuMaskCount(s)
|
||||
}
|
||||
|
||||
func (s CPUSetDynamic) size() uintptr {
|
||||
return uintptr(len(s)) * unsafe.Sizeof(cpuMask(0))
|
||||
}
|
||||
|
||||
func (s CPUSetDynamic) pointer() unsafe.Pointer {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unsafe.Pointer(&s[0])
|
||||
}
|
||||
|
||||
// SchedGetaffinityDynamic gets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
//
|
||||
// If the set is smaller than the size of the affinity mask used by the kernel,
|
||||
// [EINVAL] is returned.
|
||||
func SchedGetaffinityDynamic(pid int, set CPUSetDynamic) error {
|
||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set.size(), set.pointer())
|
||||
}
|
||||
|
||||
// SchedSetaffinityDynamic sets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
func SchedSetaffinityDynamic(pid int, set CPUSetDynamic) error {
|
||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set.size(), set.pointer())
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ if [[ "$GOOS" = "linux" ]]; then
|
||||
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
||||
set -e
|
||||
$cmd docker build --tag generate:$GOOS $GOOS
|
||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
||||
$cmd docker run --rm --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
+6
-2
@@ -2644,8 +2644,12 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
|
||||
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
|
||||
//sys Mseal(b []byte, flags uint) (err error)
|
||||
|
||||
//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY
|
||||
//sys setMemPolicy(mode int, mask unsafe.Pointer, size uintptr) (err error) = SYS_SET_MEMPOLICY
|
||||
|
||||
func SetMemPolicy(mode int, mask *CPUSet) error {
|
||||
return setMemPolicy(mode, mask, _CPU_SETSIZE)
|
||||
return setMemPolicy(mode, unsafe.Pointer(mask), _CPU_SETSIZE)
|
||||
}
|
||||
|
||||
func SetMemPolicyDynamic(mode int, mask CPUSetDynamic) error {
|
||||
return setMemPolicy(mode, mask.pointer(), mask.size())
|
||||
}
|
||||
|
||||
+3
@@ -82,6 +82,9 @@ func Time(t *Time_t) (Time_t, error) {
|
||||
}
|
||||
|
||||
func Utime(path string, buf *Utimbuf) error {
|
||||
if buf == nil {
|
||||
return Utimes(path, nil)
|
||||
}
|
||||
tv := []Timeval{
|
||||
{Sec: buf.Actime},
|
||||
{Sec: buf.Modtime},
|
||||
|
||||
+3
@@ -113,6 +113,9 @@ func Time(t *Time_t) (Time_t, error) {
|
||||
}
|
||||
|
||||
func Utime(path string, buf *Utimbuf) error {
|
||||
if buf == nil {
|
||||
return Utimes(path, nil)
|
||||
}
|
||||
tv := []Timeval{
|
||||
{Sec: buf.Actime},
|
||||
{Sec: buf.Modtime},
|
||||
|
||||
+3
@@ -150,6 +150,9 @@ func Time(t *Time_t) (Time_t, error) {
|
||||
}
|
||||
|
||||
func Utime(path string, buf *Utimbuf) error {
|
||||
if buf == nil {
|
||||
return Utimes(path, nil)
|
||||
}
|
||||
tv := []Timeval{
|
||||
{Sec: buf.Actime},
|
||||
{Sec: buf.Modtime},
|
||||
|
||||
+3
@@ -112,6 +112,9 @@ func Time(t *Time_t) (Time_t, error) {
|
||||
}
|
||||
|
||||
func Utime(path string, buf *Utimbuf) error {
|
||||
if buf == nil {
|
||||
return Utimes(path, nil)
|
||||
}
|
||||
tv := []Timeval{
|
||||
{Sec: buf.Actime},
|
||||
{Sec: buf.Modtime},
|
||||
|
||||
+2
-2
@@ -2241,8 +2241,8 @@ func Mseal(b []byte, flags uint) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func setMemPolicy(mode int, mask *CPUSet, size int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), uintptr(size))
|
||||
func setMemPolicy(mode int, mask unsafe.Pointer, size uintptr) (err error) {
|
||||
_, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(mask), uintptr(size))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
|
||||
+10
-3
@@ -892,9 +892,13 @@ const socket_error = uintptr(^uint32(0))
|
||||
//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
|
||||
//sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx
|
||||
//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex
|
||||
//sys GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) = iphlpapi.GetIfTable2Ex
|
||||
//sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2
|
||||
//sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2
|
||||
//sys GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) = iphlpapi.GetIpInterfaceEntry
|
||||
//sys GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) = iphlpapi.GetIpInterfaceTable
|
||||
//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry
|
||||
//sys GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) = iphlpapi.GetUnicastIpAddressTable
|
||||
//sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable
|
||||
//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange
|
||||
//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2
|
||||
@@ -1693,10 +1697,13 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := uint16(len(s16) * 2)
|
||||
n := len(s16) * 2
|
||||
if n > (1<<16)-1 {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return &NTUnicodeString{
|
||||
Length: n - 2, // subtract 2 bytes for the NULL terminator
|
||||
MaximumLength: n,
|
||||
Length: uint16(n) - 2, // subtract 2 bytes for the NULL terminator
|
||||
MaximumLength: uint16(n),
|
||||
Buffer: &s16[0],
|
||||
}, nil
|
||||
}
|
||||
|
||||
+29
@@ -2320,6 +2320,21 @@ type MibIfRow2 struct {
|
||||
OutQLen uint64
|
||||
}
|
||||
|
||||
// MIB_IF_TABLE_LEVEL enumeration from netioapi.h or
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ne-netioapi-mib_if_table_level.
|
||||
const (
|
||||
MibIfTableNormal = 0
|
||||
MibIfTableRaw = 1
|
||||
MibIfTableNormalWithoutStatistics = 2
|
||||
)
|
||||
|
||||
// MibIfTable2 contains a table of logical and physical interface entries. See
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_table2.
|
||||
type MibIfTable2 struct {
|
||||
NumEntries uint32
|
||||
Table [1]MibIfRow2
|
||||
}
|
||||
|
||||
// IP_ADDRESS_PREFIX stores an IP address prefix. See
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix.
|
||||
type IpAddressPrefix struct {
|
||||
@@ -2413,6 +2428,13 @@ type MibUnicastIpAddressRow struct {
|
||||
CreationTimeStamp Filetime
|
||||
}
|
||||
|
||||
// MibUnicastIpAddressTable contains a table of unicast IP address entries. See
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_table.
|
||||
type MibUnicastIpAddressTable struct {
|
||||
NumEntries uint32
|
||||
Table [1]MibUnicastIpAddressRow
|
||||
}
|
||||
|
||||
const ScopeLevelCount = 16
|
||||
|
||||
// MIB_IPINTERFACE_ROW stores interface management information for a particular IP address family on a network interface.
|
||||
@@ -2455,6 +2477,13 @@ type MibIpInterfaceRow struct {
|
||||
DisableDefaultRoutes uint8
|
||||
}
|
||||
|
||||
// MibIpInterfaceTable contains a table of IP interface entries. See
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_table.
|
||||
type MibIpInterfaceTable struct {
|
||||
NumEntries uint32
|
||||
Table [1]MibIpInterfaceRow
|
||||
}
|
||||
|
||||
// Console related constants used for the mode parameter to SetConsoleMode. See
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode for details.
|
||||
|
||||
|
||||
+36
@@ -188,9 +188,13 @@ var (
|
||||
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
|
||||
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
|
||||
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
|
||||
procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex")
|
||||
procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
|
||||
procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2")
|
||||
procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry")
|
||||
procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable")
|
||||
procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
|
||||
procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable")
|
||||
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
|
||||
procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2")
|
||||
procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
|
||||
@@ -1674,6 +1678,14 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetIfTable2Ex.Addr(), uintptr(level), uintptr(unsafe.Pointer(table)))
|
||||
if r0 != 0 {
|
||||
errcode = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row)))
|
||||
if r0 != 0 {
|
||||
@@ -1690,6 +1702,22 @@ func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode erro
|
||||
return
|
||||
}
|
||||
|
||||
func GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetIpInterfaceEntry.Addr(), uintptr(unsafe.Pointer(row)))
|
||||
if r0 != 0 {
|
||||
errcode = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetIpInterfaceTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
|
||||
if r0 != 0 {
|
||||
errcode = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row)))
|
||||
if r0 != 0 {
|
||||
@@ -1698,6 +1726,14 @@ func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) {
|
||||
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
|
||||
if r0 != 0 {
|
||||
errcode = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
|
||||
var _p0 uint32
|
||||
if initialNotification {
|
||||
|
||||
+1
-1
@@ -524,7 +524,7 @@ func (f *finder) find(T types.Type, path []byte) []byte {
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
if f.seenMethods[m] {
|
||||
return nil
|
||||
continue // break cycles (see TestIssue70418)
|
||||
}
|
||||
path2 := appendOpArg(path, opMethod, i)
|
||||
if m == f.obj {
|
||||
|
||||
Generated
Vendored
+69
-41
@@ -35,6 +35,10 @@ type pkgReader struct {
|
||||
|
||||
// laterFns holds functions that need to be invoked at the end of
|
||||
// import reading.
|
||||
//
|
||||
// TODO(mdempsky): Is it safe to have a single "later" slice or do
|
||||
// we need to have multiple passes? See comments on CL 386002 and
|
||||
// go.dev/issue/52104.
|
||||
laterFns []func()
|
||||
// laterFors is used in case of 'type A B' to ensure that B is processed before A.
|
||||
laterFors map[types.Type]int
|
||||
@@ -158,12 +162,11 @@ type reader struct {
|
||||
// A readerDict holds the state for type parameters that parameterize
|
||||
// the current unified IR element.
|
||||
type readerDict struct {
|
||||
// bounds is a slice of typeInfos corresponding to the underlying
|
||||
// bounds of the element's type parameters.
|
||||
bounds []typeInfo
|
||||
rtbounds []typeInfo // contains constraint types for each parameter in rtparams
|
||||
rtparams []*types.TypeParam // contains receiver type parameters for an element
|
||||
|
||||
// tparams is a slice of the constructed TypeParams for the element.
|
||||
tparams []*types.TypeParam
|
||||
tbounds []typeInfo // contains constraint types for each parameter in tparams
|
||||
tparams []*types.TypeParam // contains type parameters for an element
|
||||
|
||||
// derived is a slice of types derived from tparams, which may be
|
||||
// instantiated while reading the current element.
|
||||
@@ -353,7 +356,11 @@ func (r *reader) doTyp() (res types.Type) {
|
||||
return name.Type()
|
||||
|
||||
case pkgbits.TypeTypeParam:
|
||||
return r.dict.tparams[r.Len()]
|
||||
n := r.Len()
|
||||
if n < len(r.dict.rtbounds) {
|
||||
return r.dict.rtparams[n]
|
||||
}
|
||||
return r.dict.tparams[n-len(r.dict.rtbounds)]
|
||||
|
||||
case pkgbits.TypeArray:
|
||||
len := int64(r.Uint64())
|
||||
@@ -534,7 +541,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
|
||||
pos := r.pos()
|
||||
var tparams []*types.TypeParam
|
||||
if r.Version().Has(pkgbits.AliasTypeParamNames) {
|
||||
tparams = r.typeParamNames()
|
||||
tparams = r.typeParamNames(false)
|
||||
}
|
||||
typ := r.typ()
|
||||
declare(aliases.New(pos, objPkg, objName, typ, tparams))
|
||||
@@ -547,8 +554,15 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
|
||||
|
||||
case pkgbits.ObjFunc:
|
||||
pos := r.pos()
|
||||
tparams := r.typeParamNames()
|
||||
sig := r.signature(nil, nil, tparams)
|
||||
var rtparams []*types.TypeParam
|
||||
var recv *types.Var
|
||||
if r.Version().Has(pkgbits.GenericMethods) && r.Bool() {
|
||||
r.selector()
|
||||
rtparams = r.typeParamNames(true)
|
||||
recv = r.param()
|
||||
}
|
||||
tparams := r.typeParamNames(false)
|
||||
sig := r.signature(recv, rtparams, tparams)
|
||||
declare(types.NewFunc(pos, objPkg, objName, sig))
|
||||
|
||||
case pkgbits.ObjType:
|
||||
@@ -558,7 +572,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
|
||||
named := types.NewNamed(obj, nil, nil)
|
||||
declare(obj)
|
||||
|
||||
named.SetTypeParams(r.typeParamNames())
|
||||
named.SetTypeParams(r.typeParamNames(false))
|
||||
|
||||
setUnderlying := func(underlying types.Type) {
|
||||
// If the underlying type is an interface, we need to
|
||||
@@ -638,9 +652,20 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict {
|
||||
errorf("unexpected object with %v implicit type parameter(s)", implicits)
|
||||
}
|
||||
|
||||
dict.bounds = make([]typeInfo, r.Len())
|
||||
for i := range dict.bounds {
|
||||
dict.bounds[i] = r.typInfo()
|
||||
nreceivers := 0
|
||||
if r.Version().Has(pkgbits.GenericMethods) && r.Bool() {
|
||||
nreceivers = r.Len()
|
||||
}
|
||||
nexplicits := r.Len()
|
||||
|
||||
dict.rtbounds = make([]typeInfo, nreceivers)
|
||||
for i := range dict.rtbounds {
|
||||
dict.rtbounds[i] = r.typInfo()
|
||||
}
|
||||
|
||||
dict.tbounds = make([]typeInfo, nexplicits)
|
||||
for i := range dict.tbounds {
|
||||
dict.tbounds[i] = r.typInfo()
|
||||
}
|
||||
|
||||
dict.derived = make([]derivedInfo, r.Len())
|
||||
@@ -659,15 +684,24 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict {
|
||||
return &dict
|
||||
}
|
||||
|
||||
func (r *reader) typeParamNames() []*types.TypeParam {
|
||||
func (r *reader) typeParamNames(isGenMeth bool) []*types.TypeParam {
|
||||
r.Sync(pkgbits.SyncTypeParamNames)
|
||||
|
||||
// Note: This code assumes it only processes objects without
|
||||
// implement type parameters. This is currently fine, because
|
||||
// reader is only used to read in exported declarations, which are
|
||||
// always package scoped.
|
||||
// Note: This code assumes there are no implicit type parameters.
|
||||
// This is fine since it only reads exported declarations, which
|
||||
// never have implicits.
|
||||
|
||||
if len(r.dict.bounds) == 0 {
|
||||
var in []typeInfo
|
||||
var out *[]*types.TypeParam
|
||||
if isGenMeth {
|
||||
in = r.dict.rtbounds
|
||||
out = &r.dict.rtparams
|
||||
} else {
|
||||
in = r.dict.tbounds
|
||||
out = &r.dict.tparams
|
||||
}
|
||||
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -676,40 +710,34 @@ func (r *reader) typeParamNames() []*types.TypeParam {
|
||||
// create all the TypeNames and TypeParams, then we construct and
|
||||
// set the bound type.
|
||||
|
||||
r.dict.tparams = make([]*types.TypeParam, len(r.dict.bounds))
|
||||
for i := range r.dict.bounds {
|
||||
// We have to save tparams outside of the closure, because typeParamNames
|
||||
// can be called multiple times with the same dictionary instance.
|
||||
tparams := make([]*types.TypeParam, len(in))
|
||||
*out = tparams
|
||||
|
||||
for i := range in {
|
||||
pos := r.pos()
|
||||
pkg, name := r.localIdent()
|
||||
|
||||
tname := types.NewTypeName(pos, pkg, name, nil)
|
||||
r.dict.tparams[i] = types.NewTypeParam(tname, nil)
|
||||
tparams[i] = types.NewTypeParam(tname, nil)
|
||||
}
|
||||
|
||||
typs := make([]types.Type, len(r.dict.bounds))
|
||||
for i, bound := range r.dict.bounds {
|
||||
typs[i] = r.p.typIdx(bound, r.dict)
|
||||
// The reader dictionary will continue mutating before we have time
|
||||
// to call delayed functions; make a local copy of the constraints.
|
||||
types := make([]types.Type, len(in))
|
||||
for i, info := range in {
|
||||
types[i] = r.p.typIdx(info, r.dict)
|
||||
}
|
||||
|
||||
// TODO(mdempsky): This is subtle, elaborate further.
|
||||
//
|
||||
// We have to save tparams outside of the closure, because
|
||||
// typeParamNames() can be called multiple times with the same
|
||||
// dictionary instance.
|
||||
//
|
||||
// Also, this needs to happen later to make sure SetUnderlying has
|
||||
// been called.
|
||||
//
|
||||
// TODO(mdempsky): Is it safe to have a single "later" slice or do
|
||||
// we need to have multiple passes? See comments on CL 386002 and
|
||||
// go.dev/issue/52104.
|
||||
tparams := r.dict.tparams
|
||||
// This needs to happen later to make sure SetUnderlying has been called.
|
||||
r.p.later(func() {
|
||||
for i, typ := range typs {
|
||||
for i, typ := range types {
|
||||
tparams[i].SetConstraint(typ)
|
||||
}
|
||||
})
|
||||
|
||||
return r.dict.tparams
|
||||
return tparams
|
||||
}
|
||||
|
||||
func (r *reader) method() *types.Func {
|
||||
@@ -717,7 +745,7 @@ func (r *reader) method() *types.Func {
|
||||
pos := r.pos()
|
||||
pkg, name := r.selector()
|
||||
|
||||
rparams := r.typeParamNames()
|
||||
rparams := r.typeParamNames(false)
|
||||
sig := r.signature(r.param(), rparams, nil)
|
||||
|
||||
_ = r.pos() // TODO(mdempsky): Remove; this is a hacker for linker.go.
|
||||
+3
@@ -26,6 +26,9 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) {
|
||||
inv.BuildFlags = nil // This is not a build command.
|
||||
inv.ModFlag = ""
|
||||
inv.ModFile = ""
|
||||
// Set GO111MODULE=off so that we are immune to errors in go.{work,mod}.
|
||||
// Unfortunately, this breaks the Go 1.21+ toolchain directive and
|
||||
// may affect the set of ReleaseTags; see #68495.
|
||||
inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off")
|
||||
|
||||
stdoutBytes, err := r.Run(ctx, inv)
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// Copyright 2024 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 imports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/modindex"
|
||||
)
|
||||
|
||||
// This code is here rather than in the modindex package
|
||||
// to avoid import loops
|
||||
|
||||
// TODO(adonovan): this code is only used by a test in this package.
|
||||
// Can we delete it? Or is there a plan to call NewIndexSource from
|
||||
// cmd/goimports?
|
||||
|
||||
// implements Source using modindex, so only for module cache.
|
||||
//
|
||||
// this is perhaps over-engineered. A new Index is read at first use.
|
||||
// And then Update is called after every 15 minutes, and a new Index
|
||||
// is read if the index changed. It is not clear the Mutex is needed.
|
||||
type IndexSource struct {
|
||||
modcachedir string
|
||||
mu sync.Mutex
|
||||
index *modindex.Index // (access via getIndex)
|
||||
expires time.Time
|
||||
}
|
||||
|
||||
// create a new Source. Called from NewView in cache/session.go.
|
||||
func NewIndexSource(cachedir string) *IndexSource {
|
||||
return &IndexSource{modcachedir: cachedir}
|
||||
}
|
||||
|
||||
func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) {
|
||||
/// This is used by goimports to resolve the package names of imports of the
|
||||
// current package, which is irrelevant for the module cache.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) {
|
||||
index, err := s.getIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cs []modindex.Candidate
|
||||
for pkg, nms := range missing {
|
||||
for nm := range nms {
|
||||
x := index.Lookup(pkg, nm, false)
|
||||
cs = append(cs, x...)
|
||||
}
|
||||
}
|
||||
found := make(map[string]*Result)
|
||||
for _, c := range cs {
|
||||
var x *Result
|
||||
if x = found[c.ImportPath]; x == nil {
|
||||
x = &Result{
|
||||
Import: &ImportInfo{
|
||||
ImportPath: c.ImportPath,
|
||||
Name: "",
|
||||
},
|
||||
Package: &PackageInfo{
|
||||
Name: c.PkgName,
|
||||
Exports: make(map[string]bool),
|
||||
},
|
||||
}
|
||||
found[c.ImportPath] = x
|
||||
}
|
||||
x.Package.Exports[c.Name] = true
|
||||
}
|
||||
var ans []*Result
|
||||
for _, x := range found {
|
||||
ans = append(ans, x)
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func (s *IndexSource) getIndex() (*modindex.Index, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// (s.index = nil => s.expires is zero,
|
||||
// so the first condition is strictly redundant.
|
||||
// But it makes the postcondition very clear.)
|
||||
if s.index == nil || time.Now().After(s.expires) {
|
||||
index, err := modindex.Update(s.modcachedir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.index = index
|
||||
s.expires = index.ValidAt.Add(15 * time.Minute) // (refresh period)
|
||||
}
|
||||
// Inv: s.index != nil
|
||||
|
||||
return s.index, nil
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
// Copyright 2024 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 modindex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
"golang.org/x/tools/internal/gopathwalk"
|
||||
)
|
||||
|
||||
type directory struct {
|
||||
path string // relative to GOMODCACHE
|
||||
importPath string
|
||||
version string // semantic version
|
||||
}
|
||||
|
||||
// bestDirByImportPath returns the best directory for each import
|
||||
// path, where "best" means most recent semantic version. These import
|
||||
// paths are inferred from the GOMODCACHE-relative dir names in dirs.
|
||||
func bestDirByImportPath(dirs []string) (map[string]directory, error) {
|
||||
dirsByPath := make(map[string]directory)
|
||||
for _, dir := range dirs {
|
||||
importPath, version, err := dirToImportPathVersion(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
new := directory{
|
||||
path: dir,
|
||||
importPath: importPath,
|
||||
version: version,
|
||||
}
|
||||
if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 {
|
||||
dirsByPath[importPath] = new
|
||||
}
|
||||
}
|
||||
return dirsByPath, nil
|
||||
}
|
||||
|
||||
// compareDirectory defines an ordering of path@version directories,
|
||||
// by descending version, then by ascending path.
|
||||
func compareDirectory(x, y directory) int {
|
||||
if sign := -semver.Compare(x.version, y.version); sign != 0 {
|
||||
return sign // latest first
|
||||
}
|
||||
return strings.Compare(string(x.path), string(y.path))
|
||||
}
|
||||
|
||||
// modCacheRegexp splits a relpathpath into module, module version, and package.
|
||||
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
|
||||
|
||||
// dirToImportPathVersion computes import path and semantic version
|
||||
// from a GOMODCACHE-relative directory name.
|
||||
func dirToImportPathVersion(dir string) (string, string, error) {
|
||||
m := modCacheRegexp.FindStringSubmatch(string(dir))
|
||||
// m[1] is the module path
|
||||
// m[2] is the version major.minor.patch(-<pre release identifier)
|
||||
// m[3] is the rest of the package path
|
||||
if len(m) != 4 {
|
||||
return "", "", fmt.Errorf("bad dir %s", dir)
|
||||
}
|
||||
if !semver.IsValid(m[2]) {
|
||||
return "", "", fmt.Errorf("bad semantic version %s", m[2])
|
||||
}
|
||||
// ToSlash is required to convert Windows file paths
|
||||
// into Go package import paths.
|
||||
return filepath.ToSlash(m[1] + m[3]), m[2], nil
|
||||
}
|
||||
|
||||
// findDirs returns an unordered list of relevant package directories,
|
||||
// relative to the specified module cache root. The result includes only
|
||||
// module dirs whose mtime is within (start, end).
|
||||
func findDirs(root string, start, end time.Time) []string {
|
||||
var (
|
||||
resMu sync.Mutex
|
||||
res []string
|
||||
)
|
||||
|
||||
addDir := func(root gopathwalk.Root, dir string) {
|
||||
// TODO(pjw): do we need to check times?
|
||||
resMu.Lock()
|
||||
defer resMu.Unlock()
|
||||
res = append(res, relative(root.Path, dir))
|
||||
}
|
||||
|
||||
skipDir := func(_ gopathwalk.Root, dir string) bool {
|
||||
// The cache directory is already ignored in gopathwalk.
|
||||
if filepath.Base(dir) == "internal" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Skip toolchains.
|
||||
if strings.Contains(dir, "toolchain@") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Don't look inside @ directories that are too old/new.
|
||||
if strings.Contains(filepath.Base(dir), "@") {
|
||||
st, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
log.Printf("can't stat dir %s %v", dir, err)
|
||||
return true
|
||||
}
|
||||
mtime := st.ModTime()
|
||||
return mtime.Before(start) || mtime.After(end)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(adonovan): parallelize this. Even with a hot buffer cache,
|
||||
// find $(go env GOMODCACHE) -type d
|
||||
// can easily take up a minute.
|
||||
roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}}
|
||||
gopathwalk.WalkSkip(roots, addDir, skipDir, gopathwalk.Options{
|
||||
ModulesEnabled: true,
|
||||
Concurrency: 1, // TODO(pjw): adjust concurrency
|
||||
// Logf: log.Printf,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
-292
@@ -1,292 +0,0 @@
|
||||
// Copyright 2024 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 modindex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
The on-disk index ("payload") is a text file.
|
||||
The first 3 lines are header information containing CurrentVersion,
|
||||
the value of GOMODCACHE, and the validity date of the index.
|
||||
(This is when the code started building the index.)
|
||||
Following the header are sections of lines, one section for each
|
||||
import path. These sections are sorted by package name.
|
||||
The first line of each section, marked by a leading :, contains
|
||||
the package name, the import path, the name of the directory relative
|
||||
to GOMODCACHE, and its semantic version.
|
||||
The rest of each section consists of one line per exported symbol.
|
||||
The lines are sorted by the symbol's name and contain the name,
|
||||
an indication of its lexical type (C, T, V, F), and if it is the
|
||||
name of a function, information about the signature.
|
||||
|
||||
The fields in the section header lines are separated by commas, and
|
||||
in the unlikely event this would be confusing, the csv package is used
|
||||
to write (and read) them.
|
||||
|
||||
In the lines containing exported names, C=const, V=var, T=type, F=func.
|
||||
If it is a func, the next field is the number of returned values,
|
||||
followed by pairs consisting of formal parameter names and types.
|
||||
All these fields are separated by spaces. Any spaces in a type
|
||||
(e.g., chan struct{}) are replaced by $s on the disk. The $s are
|
||||
turned back into spaces when read.
|
||||
|
||||
Here is an index header (the comments are not part of the index):
|
||||
0 // version (of the index format)
|
||||
/usr/local/google/home/pjw/go/pkg/mod // GOMODCACHE
|
||||
2024-09-11 18:55:09 // validity date of the index
|
||||
|
||||
Here is an index section:
|
||||
:yaml,gopkg.in/yaml.v1,gopkg.in/yaml.v1@v1.0.0-20140924161607-9f9df34309c0,v1.0.0-20140924161607-9f9df34309c0
|
||||
Getter T
|
||||
Marshal F 2 in interface{}
|
||||
Setter T
|
||||
Unmarshal F 1 in []byte out interface{}
|
||||
|
||||
The package name is yaml, the import path is gopkg.in/yaml.v1.
|
||||
Getter and Setter are types, and Marshal and Unmarshal are functions.
|
||||
The latter returns one value and has two arguments, 'in' and 'out'
|
||||
whose types are []byte and interface{}.
|
||||
*/
|
||||
|
||||
// CurrentVersion tells readers about the format of the index.
|
||||
const CurrentVersion int = 0
|
||||
|
||||
// Index is returned by [Read].
|
||||
type Index struct {
|
||||
Version int
|
||||
GOMODCACHE string // absolute path of Go module cache dir
|
||||
ValidAt time.Time // moment at which the index was up to date
|
||||
Entries []Entry
|
||||
}
|
||||
|
||||
func (ix *Index) String() string {
|
||||
return fmt.Sprintf("Index(%s v%d has %d entries at %v)",
|
||||
ix.GOMODCACHE, ix.Version, len(ix.Entries), ix.ValidAt)
|
||||
}
|
||||
|
||||
// An Entry contains information for an import path.
|
||||
type Entry struct {
|
||||
Dir string // package directory relative to GOMODCACHE; uses OS path separator
|
||||
ImportPath string
|
||||
PkgName string
|
||||
Version string
|
||||
Names []string // exported names and information
|
||||
}
|
||||
|
||||
// IndexDir is where the module index is stored.
|
||||
// Each logical index entry consists of a pair of files:
|
||||
//
|
||||
// - the "payload" (index-VERSION-XXX), whose name is
|
||||
// randomized, holds the actual index; and
|
||||
// - the "link" (index-name-VERSION-HASH),
|
||||
// whose name is predictable, contains the
|
||||
// name of the payload file.
|
||||
//
|
||||
// Since the link file is small (<512B),
|
||||
// reads and writes to it may be assumed atomic.
|
||||
var IndexDir string = func() string {
|
||||
var dir string
|
||||
if testing.Testing() {
|
||||
dir = os.TempDir()
|
||||
} else {
|
||||
var err error
|
||||
dir, err = os.UserCacheDir()
|
||||
// shouldn't happen, but TempDir is better than
|
||||
// creating ./goimports
|
||||
if err != nil {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
}
|
||||
dir = filepath.Join(dir, "goimports")
|
||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||
dir = "" // #75505, people complain about the error message
|
||||
}
|
||||
return dir
|
||||
}()
|
||||
|
||||
// Read reads the latest version of the on-disk index
|
||||
// for the specified Go module cache directory.
|
||||
// If there is no index, it returns a nil Index and an fs.ErrNotExist error.
|
||||
func Read(gomodcache string) (*Index, error) {
|
||||
gomodcache, err := filepath.Abs(gomodcache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if IndexDir == "" {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// Read the "link" file for the specified gomodcache directory.
|
||||
// It names the payload file.
|
||||
content, err := os.ReadFile(filepath.Join(IndexDir, linkFileBasename(gomodcache)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payloadFile := filepath.Join(IndexDir, string(content))
|
||||
|
||||
// Read the index out of the payload file.
|
||||
f, err := os.Open(payloadFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return readIndexFrom(gomodcache, bufio.NewReader(f))
|
||||
}
|
||||
|
||||
func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) {
|
||||
scan := bufio.NewScanner(r)
|
||||
|
||||
// version
|
||||
if !scan.Scan() {
|
||||
return nil, fmt.Errorf("unexpected scan error: %v", scan.Err())
|
||||
}
|
||||
version, err := strconv.Atoi(scan.Text())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != CurrentVersion {
|
||||
return nil, fmt.Errorf("got version %d, expected %d", version, CurrentVersion)
|
||||
}
|
||||
|
||||
// gomodcache
|
||||
if !scan.Scan() {
|
||||
return nil, fmt.Errorf("scanner error reading module cache dir: %v", scan.Err())
|
||||
}
|
||||
// TODO(pjw): need to check that this is the expected cache dir
|
||||
// so the tag should be passed in to this function
|
||||
if dir := string(scan.Text()); dir != gomodcache {
|
||||
return nil, fmt.Errorf("index file GOMODCACHE mismatch: got %q, want %q", dir, gomodcache)
|
||||
}
|
||||
|
||||
// changed
|
||||
if !scan.Scan() {
|
||||
return nil, fmt.Errorf("scanner error reading index creation time: %v", scan.Err())
|
||||
}
|
||||
changed, err := time.ParseInLocation(time.DateTime, scan.Text(), time.Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// entries
|
||||
var (
|
||||
curEntry *Entry
|
||||
entries []Entry
|
||||
)
|
||||
for scan.Scan() {
|
||||
v := scan.Text()
|
||||
if v[0] == ':' {
|
||||
if curEntry != nil {
|
||||
entries = append(entries, *curEntry)
|
||||
}
|
||||
// as directories may contain commas and quotes, they need to be read as csv.
|
||||
rdr := strings.NewReader(v[1:])
|
||||
cs := csv.NewReader(rdr)
|
||||
flds, err := cs.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(flds) != 4 {
|
||||
return nil, fmt.Errorf("header contains %d fields, not 4: %q", len(v), v)
|
||||
}
|
||||
curEntry = &Entry{
|
||||
PkgName: flds[0],
|
||||
ImportPath: flds[1],
|
||||
Dir: relative(gomodcache, flds[2]),
|
||||
Version: flds[3],
|
||||
}
|
||||
continue
|
||||
}
|
||||
curEntry.Names = append(curEntry.Names, v)
|
||||
}
|
||||
if err := scan.Err(); err != nil {
|
||||
return nil, fmt.Errorf("scanner failed while reading modindex entry: %v", err)
|
||||
}
|
||||
if curEntry != nil {
|
||||
entries = append(entries, *curEntry)
|
||||
}
|
||||
|
||||
return &Index{
|
||||
Version: version,
|
||||
GOMODCACHE: gomodcache,
|
||||
ValidAt: changed,
|
||||
Entries: entries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// write writes the index file and updates the index directory to refer to it.
|
||||
func write(gomodcache string, ix *Index) error {
|
||||
if IndexDir == "" {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
// Write the index into a payload file with a fresh name.
|
||||
f, err := os.CreateTemp(IndexDir, fmt.Sprintf("index-%d-*", CurrentVersion))
|
||||
if err != nil {
|
||||
return err // e.g. disk full, or index dir deleted
|
||||
}
|
||||
if err := writeIndexToFile(ix, bufio.NewWriter(f)); err != nil {
|
||||
_ = f.Close() // ignore error
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the name of the payload file into a link file.
|
||||
indexDirFile := filepath.Join(IndexDir, linkFileBasename(gomodcache))
|
||||
content := []byte(filepath.Base(f.Name()))
|
||||
return os.WriteFile(indexDirFile, content, 0666)
|
||||
}
|
||||
|
||||
func writeIndexToFile(x *Index, w *bufio.Writer) error {
|
||||
fmt.Fprintf(w, "%d\n", x.Version)
|
||||
fmt.Fprintf(w, "%s\n", x.GOMODCACHE)
|
||||
tm := x.ValidAt.Truncate(time.Second) // round the time down
|
||||
fmt.Fprintf(w, "%s\n", tm.Format(time.DateTime))
|
||||
for _, e := range x.Entries {
|
||||
if e.ImportPath == "" {
|
||||
continue // shouldn't happen
|
||||
}
|
||||
// PJW: maybe always write these headers as csv?
|
||||
if strings.ContainsAny(string(e.Dir), ",\"") {
|
||||
cw := csv.NewWriter(w)
|
||||
cw.Write([]string{":" + e.PkgName, e.ImportPath, string(e.Dir), e.Version})
|
||||
cw.Flush()
|
||||
} else {
|
||||
fmt.Fprintf(w, ":%s,%s,%s,%s\n", e.PkgName, e.ImportPath, e.Dir, e.Version)
|
||||
}
|
||||
for _, x := range e.Names {
|
||||
fmt.Fprintf(w, "%s\n", x)
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// linkFileBasename returns the base name of the link file in the
|
||||
// index directory that holds the name of the payload file for the
|
||||
// specified (absolute) Go module cache dir.
|
||||
func linkFileBasename(gomodcache string) string {
|
||||
// Note: coupled to logic in ./gomodindex/cmd.go. TODO: factor.
|
||||
h := sha256.Sum256([]byte(gomodcache)) // collision-resistant hash
|
||||
return fmt.Sprintf("index-name-%d-%032x", CurrentVersion, h)
|
||||
}
|
||||
|
||||
func relative(base, file string) string {
|
||||
if rel, err := filepath.Rel(base, file); err == nil {
|
||||
return rel
|
||||
}
|
||||
return file
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
// Copyright 2024 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 modindex
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
||||
type Candidate struct {
|
||||
PkgName string
|
||||
Name string
|
||||
Dir string
|
||||
ImportPath string
|
||||
Type LexType
|
||||
Deprecated bool
|
||||
// information for Funcs
|
||||
Results int16 // how many results
|
||||
Sig []Field // arg names and types
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Arg, Type string
|
||||
}
|
||||
|
||||
type LexType int8
|
||||
|
||||
const (
|
||||
Const LexType = iota
|
||||
Var
|
||||
Type
|
||||
Func
|
||||
)
|
||||
|
||||
// LookupAll only returns those Candidates whose import path
|
||||
// finds all the names.
|
||||
func (ix *Index) LookupAll(pkgName string, names ...string) map[string][]Candidate {
|
||||
// this can be made faster when benchmarks show that it needs to be
|
||||
names = uniquify(names)
|
||||
byImpPath := make(map[string][]Candidate)
|
||||
for _, nm := range names {
|
||||
cands := ix.Lookup(pkgName, nm, false)
|
||||
for _, c := range cands {
|
||||
byImpPath[c.ImportPath] = append(byImpPath[c.ImportPath], c)
|
||||
}
|
||||
}
|
||||
for k, v := range byImpPath {
|
||||
if len(v) != len(names) {
|
||||
delete(byImpPath, k)
|
||||
}
|
||||
}
|
||||
return byImpPath
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
func uniquify(in []string) []string {
|
||||
if len(in) == 0 {
|
||||
return in
|
||||
}
|
||||
in = slices.Clone(in)
|
||||
slices.Sort(in)
|
||||
return slices.Compact(in)
|
||||
}
|
||||
|
||||
// Lookup finds all the symbols in the index with the given PkgName and name.
|
||||
// If prefix is true, it finds all of these with name as a prefix.
|
||||
func (ix *Index) Lookup(pkgName, name string, prefix bool) []Candidate {
|
||||
loc, ok := slices.BinarySearchFunc(ix.Entries, pkgName, func(e Entry, pkg string) int {
|
||||
return strings.Compare(e.PkgName, pkgName)
|
||||
})
|
||||
if !ok {
|
||||
return nil // didn't find the package
|
||||
}
|
||||
var ans []Candidate
|
||||
// loc is the first entry for this package name, but there may be several
|
||||
for i := loc; i < len(ix.Entries); i++ {
|
||||
e := ix.Entries[i]
|
||||
if e.PkgName != pkgName {
|
||||
break // end of sorted package names
|
||||
}
|
||||
nloc, ok := slices.BinarySearchFunc(e.Names, name, func(s string, name string) int {
|
||||
if strings.HasPrefix(s, name) {
|
||||
return 0
|
||||
}
|
||||
if s < name {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
if !ok {
|
||||
continue // didn't find the name, nor any symbols with name as a prefix
|
||||
}
|
||||
for j := nloc; j < len(e.Names); j++ {
|
||||
nstr := e.Names[j]
|
||||
// benchmarks show this makes a difference when there are a lot of Possibilities
|
||||
flds := fastSplit(nstr)
|
||||
if !(flds[0] == name || prefix && strings.HasPrefix(flds[0], name)) {
|
||||
// past range of matching Names
|
||||
break
|
||||
}
|
||||
if len(flds) < 2 {
|
||||
continue // should never happen
|
||||
}
|
||||
impPath, err := module.UnescapePath(e.ImportPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
px := Candidate{
|
||||
PkgName: pkgName,
|
||||
Name: flds[0],
|
||||
Dir: string(e.Dir),
|
||||
ImportPath: impPath,
|
||||
Type: asLexType(flds[1][0]),
|
||||
Deprecated: len(flds[1]) > 1 && flds[1][1] == 'D',
|
||||
}
|
||||
if px.Type == Func {
|
||||
n, err := strconv.Atoi(flds[2])
|
||||
if err != nil {
|
||||
continue // should never happen
|
||||
}
|
||||
px.Results = int16(n)
|
||||
if len(flds) >= 4 {
|
||||
sig := strings.Split(flds[3], " ")
|
||||
for i := range sig {
|
||||
// $ cannot otherwise occur. removing the spaces
|
||||
// almost works, but for chan struct{}, e.g.
|
||||
sig[i] = strings.Replace(sig[i], "$", " ", -1)
|
||||
}
|
||||
px.Sig = toFields(sig)
|
||||
}
|
||||
}
|
||||
ans = append(ans, px)
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func toFields(sig []string) []Field {
|
||||
ans := make([]Field, len(sig)/2)
|
||||
for i := range ans {
|
||||
ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
// benchmarks show this is measurably better than strings.Split
|
||||
// split into first 4 fields separated by single space
|
||||
func fastSplit(x string) []string {
|
||||
ans := make([]string, 0, 4)
|
||||
nxt := 0
|
||||
start := 0
|
||||
for i := 0; i < len(x); i++ {
|
||||
if x[i] != ' ' {
|
||||
continue
|
||||
}
|
||||
ans = append(ans, x[start:i])
|
||||
nxt++
|
||||
start = i + 1
|
||||
if nxt >= 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
ans = append(ans, x[start:])
|
||||
return ans
|
||||
}
|
||||
|
||||
func asLexType(c byte) LexType {
|
||||
switch c {
|
||||
case 'C':
|
||||
return Const
|
||||
case 'V':
|
||||
return Var
|
||||
case 'T':
|
||||
return Type
|
||||
case 'F':
|
||||
return Func
|
||||
}
|
||||
return -1
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
// Copyright 2024 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 modindex contains code for building and searching an
|
||||
// [Index] of the Go module cache.
|
||||
package modindex
|
||||
|
||||
// The directory containing the index, returned by
|
||||
// [IndexDir], contains a file index-name-<ver> that contains the name
|
||||
// of the current index. We believe writing that short file is atomic.
|
||||
// [Read] reads that file to get the file name of the index.
|
||||
// WriteIndex writes an index with a unique name and then
|
||||
// writes that name into a new version of index-name-<ver>.
|
||||
// (<ver> stands for the CurrentVersion of the index format.)
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// Update updates the index for the specified Go
|
||||
// module cache directory, creating it as needed.
|
||||
// On success it returns the current index.
|
||||
func Update(gomodcache string) (*Index, error) {
|
||||
prev, err := Read(gomodcache)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
prev = nil
|
||||
}
|
||||
return update(gomodcache, prev)
|
||||
}
|
||||
|
||||
// update builds, writes, and returns the current index.
|
||||
//
|
||||
// If old is nil, the new index is built from all of GOMODCACHE;
|
||||
// otherwise it is built from the old index plus cache updates
|
||||
// since the previous index's time.
|
||||
func update(gomodcache string, old *Index) (*Index, error) {
|
||||
gomodcache, err := filepath.Abs(gomodcache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
new, changed, err := build(gomodcache, old)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if old == nil || changed {
|
||||
if err := write(gomodcache, new); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// build returns a new index for the specified Go module cache (an
|
||||
// absolute path).
|
||||
//
|
||||
// If an old index is provided, only directories more recent than it
|
||||
// that it are scanned; older directories are provided by the old
|
||||
// Index.
|
||||
//
|
||||
// The boolean result indicates whether new entries were found.
|
||||
func build(gomodcache string, old *Index) (*Index, bool, error) {
|
||||
// Set the time window.
|
||||
var start time.Time // = dawn of time
|
||||
if old != nil {
|
||||
start = old.ValidAt
|
||||
}
|
||||
now := time.Now()
|
||||
end := now.Add(24 * time.Hour) // safely in the future
|
||||
|
||||
// Enumerate GOMODCACHE package directories.
|
||||
// Choose the best (latest) package for each import path.
|
||||
pkgDirs := findDirs(gomodcache, start, end)
|
||||
dirByPath, err := bestDirByImportPath(pkgDirs)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// For each import path it might occur only in
|
||||
// dirByPath, only in old, or in both.
|
||||
// If both, use the semantically later one.
|
||||
var entries []Entry
|
||||
if old != nil {
|
||||
for _, entry := range old.Entries {
|
||||
dir, ok := dirByPath[entry.ImportPath]
|
||||
if !ok || semver.Compare(dir.version, entry.Version) <= 0 {
|
||||
// New dir is missing or not more recent; use old entry.
|
||||
entries = append(entries, entry)
|
||||
delete(dirByPath, entry.ImportPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract symbol information for all the new directories.
|
||||
newEntries := extractSymbols(gomodcache, maps.Values(dirByPath))
|
||||
entries = append(entries, newEntries...)
|
||||
slices.SortFunc(entries, func(x, y Entry) int {
|
||||
if n := strings.Compare(x.PkgName, y.PkgName); n != 0 {
|
||||
return n
|
||||
}
|
||||
return strings.Compare(x.ImportPath, y.ImportPath)
|
||||
})
|
||||
|
||||
return &Index{
|
||||
GOMODCACHE: gomodcache,
|
||||
ValidAt: now, // time before the directories were scanned
|
||||
Entries: entries,
|
||||
}, len(newEntries) > 0, nil
|
||||
}
|
||||
-244
@@ -1,244 +0,0 @@
|
||||
// Copyright 2024 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 modindex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"iter"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// The name of a symbol contains information about the symbol:
|
||||
// <name> T for types, TD if the type is deprecated
|
||||
// <name> C for consts, CD if the const is deprecated
|
||||
// <name> V for vars, VD if the var is deprecated
|
||||
// and for funcs: <name> F <num of return values> (<arg-name> <arg-type>)*
|
||||
// any spaces in <arg-type> are replaced by $s so that the fields
|
||||
// of the name are space separated. F is replaced by FD if the func
|
||||
// is deprecated.
|
||||
type symbol struct {
|
||||
pkg string // name of the symbols's package
|
||||
name string // declared name
|
||||
kind string // T, C, V, or F, followed by D if deprecated
|
||||
sig string // signature information, for F
|
||||
}
|
||||
|
||||
// extractSymbols returns a (new, unordered) array of Entries, one for
|
||||
// each provided package directory, describing its exported symbols.
|
||||
func extractSymbols(cwd string, dirs iter.Seq[directory]) []Entry {
|
||||
var (
|
||||
mu sync.Mutex
|
||||
entries []Entry
|
||||
)
|
||||
|
||||
var g errgroup.Group
|
||||
g.SetLimit(max(2, runtime.GOMAXPROCS(0)/2))
|
||||
for dir := range dirs {
|
||||
g.Go(func() error {
|
||||
thedir := filepath.Join(cwd, string(dir.path))
|
||||
mode := parser.SkipObjectResolution | parser.ParseComments
|
||||
|
||||
// Parse all Go files in dir and extract symbols.
|
||||
dirents, err := os.ReadDir(thedir)
|
||||
if err != nil {
|
||||
return nil // log this someday?
|
||||
}
|
||||
var syms []symbol
|
||||
for _, dirent := range dirents {
|
||||
if !strings.HasSuffix(dirent.Name(), ".go") ||
|
||||
strings.HasSuffix(dirent.Name(), "_test.go") {
|
||||
continue
|
||||
}
|
||||
fname := filepath.Join(thedir, dirent.Name())
|
||||
tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode)
|
||||
if err != nil {
|
||||
continue // ignore errors, someday log them?
|
||||
}
|
||||
syms = append(syms, getFileExports(tr)...)
|
||||
}
|
||||
|
||||
// Create an entry for the package.
|
||||
pkg, names := processSyms(syms)
|
||||
if pkg != "" {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
entries = append(entries, Entry{
|
||||
PkgName: pkg,
|
||||
Dir: dir.path,
|
||||
ImportPath: dir.importPath,
|
||||
Version: dir.version,
|
||||
Names: names,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
g.Wait() // ignore error
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func getFileExports(f *ast.File) []symbol {
|
||||
pkg := f.Name.Name
|
||||
if pkg == "main" || pkg == "" {
|
||||
return nil
|
||||
}
|
||||
var ans []symbol
|
||||
// should we look for //go:build ignore?
|
||||
for _, decl := range f.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv != nil {
|
||||
// ignore methods, as we are completing package selections
|
||||
continue
|
||||
}
|
||||
name := decl.Name.Name
|
||||
dtype := decl.Type
|
||||
// not looking at dtype.TypeParams. That is, treating
|
||||
// generic functions just like non-generic ones.
|
||||
sig := dtype.Params
|
||||
kind := "F"
|
||||
if isDeprecated(decl.Doc) {
|
||||
kind += "D"
|
||||
}
|
||||
result := []string{fmt.Sprintf("%d", dtype.Results.NumFields())}
|
||||
for _, x := range sig.List {
|
||||
// This code creates a string representing the type.
|
||||
// TODO(pjw): it may be fragile:
|
||||
// 1. x.Type could be nil, perhaps in ill-formed code
|
||||
// 2. ExprString might someday change incompatibly to
|
||||
// include struct tags, which can be arbitrary strings
|
||||
if x.Type == nil {
|
||||
// Can this happen without a parse error? (Files with parse
|
||||
// errors are ignored in getSymbols)
|
||||
continue // maybe report this someday
|
||||
}
|
||||
tp := types.ExprString(x.Type)
|
||||
if len(tp) == 0 {
|
||||
// Can this happen?
|
||||
continue // maybe report this someday
|
||||
}
|
||||
// This is only safe if ExprString never returns anything with a $
|
||||
// The only place a $ can occur seems to be in a struct tag, which
|
||||
// can be an arbitrary string literal, and ExprString does not presently
|
||||
// print struct tags. So for this to happen the type of a formal parameter
|
||||
// has to be a explicit struct, e.g. foo(x struct{a int "$"}) and ExprString
|
||||
// would have to show the struct tag. Even testing for this case seems
|
||||
// a waste of effort, but let's remember the possibility
|
||||
if strings.Contains(tp, "$") {
|
||||
continue
|
||||
}
|
||||
tp = strings.Replace(tp, " ", "$", -1)
|
||||
if len(x.Names) == 0 {
|
||||
result = append(result, "_")
|
||||
result = append(result, tp)
|
||||
} else {
|
||||
for _, y := range x.Names {
|
||||
result = append(result, y.Name)
|
||||
result = append(result, tp)
|
||||
}
|
||||
}
|
||||
}
|
||||
sigs := strings.Join(result, " ")
|
||||
if s := newsym(pkg, name, kind, sigs); s != nil {
|
||||
ans = append(ans, *s)
|
||||
}
|
||||
case *ast.GenDecl:
|
||||
depr := isDeprecated(decl.Doc)
|
||||
switch decl.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
tp := "V"
|
||||
if decl.Tok == token.CONST {
|
||||
tp = "C"
|
||||
}
|
||||
if depr {
|
||||
tp += "D"
|
||||
}
|
||||
for _, sp := range decl.Specs {
|
||||
for _, x := range sp.(*ast.ValueSpec).Names {
|
||||
if s := newsym(pkg, x.Name, tp, ""); s != nil {
|
||||
ans = append(ans, *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
case token.TYPE:
|
||||
tp := "T"
|
||||
if depr {
|
||||
tp += "D"
|
||||
}
|
||||
for _, sp := range decl.Specs {
|
||||
if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, tp, ""); s != nil {
|
||||
ans = append(ans, *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func newsym(pkg, name, kind, sig string) *symbol {
|
||||
if len(name) == 0 || !ast.IsExported(name) {
|
||||
return nil
|
||||
}
|
||||
sym := symbol{pkg: pkg, name: name, kind: kind, sig: sig}
|
||||
return &sym
|
||||
}
|
||||
|
||||
func isDeprecated(doc *ast.CommentGroup) bool {
|
||||
if doc == nil {
|
||||
return false
|
||||
}
|
||||
// go.dev/wiki/Deprecated Paragraph starting 'Deprecated:'
|
||||
// This code fails for /* Deprecated: */, but it's the code from
|
||||
// gopls/internal/analysis/deprecated
|
||||
for line := range strings.SplitSeq(doc.Text(), "\n\n") {
|
||||
if strings.HasPrefix(line, "Deprecated:") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// return the package name and the value for the symbols.
|
||||
// if there are multiple packages, choose one arbitrarily
|
||||
// the returned slice is sorted lexicographically
|
||||
func processSyms(syms []symbol) (string, []string) {
|
||||
if len(syms) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
slices.SortFunc(syms, func(l, r symbol) int {
|
||||
return strings.Compare(l.name, r.name)
|
||||
})
|
||||
pkg := syms[0].pkg
|
||||
var names []string
|
||||
for _, s := range syms {
|
||||
if s.pkg != pkg {
|
||||
// Symbols came from two files in same dir
|
||||
// with different package declarations.
|
||||
continue
|
||||
}
|
||||
var nx string
|
||||
if s.sig != "" {
|
||||
nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig)
|
||||
} else {
|
||||
nx = fmt.Sprintf("%s %s", s.name, s.kind)
|
||||
}
|
||||
names = append(names, nx)
|
||||
}
|
||||
return pkg, names
|
||||
}
|
||||
+17
@@ -28,6 +28,15 @@ const (
|
||||
// - remove derived info "needed" bool
|
||||
V2
|
||||
|
||||
// V3: introduces a more compact format for composite literal element lists
|
||||
// - negative lengths indicate that (some) elements may have keys
|
||||
// - positive lengths indicate that no element has a key
|
||||
// - a negative struct field index indicates an embedded field
|
||||
V3
|
||||
|
||||
// V4: encodes generic methods as standalone function objects
|
||||
V4
|
||||
|
||||
numVersions = iota
|
||||
)
|
||||
|
||||
@@ -61,6 +70,12 @@ const (
|
||||
// whether a type was a derived type.
|
||||
DerivedInfoNeeded
|
||||
|
||||
// Composite literals use a more compact format for element lists.
|
||||
CompactCompLiterals
|
||||
|
||||
// Generic methods may appear as standalone function objects.
|
||||
GenericMethods
|
||||
|
||||
numFields = iota
|
||||
)
|
||||
|
||||
@@ -68,6 +83,8 @@ const (
|
||||
var introduced = [numFields]Version{
|
||||
Flags: V1,
|
||||
AliasTypeParamNames: V2,
|
||||
CompactCompLiterals: V3,
|
||||
GenericMethods: V4,
|
||||
}
|
||||
|
||||
// removed is the version a field was removed in or 0 for fields
|
||||
|
||||
+5
-3
@@ -11,7 +11,9 @@ import (
|
||||
|
||||
// CoreType returns the core type of T or nil if T does not have a core type.
|
||||
//
|
||||
// See https://go.dev/ref/spec#Core_types for the definition of a core type.
|
||||
// As of Go1.25, the notion of a core type has been removed from the language spec.
|
||||
// See https://go.dev/blog/coretypes for more details.
|
||||
// TODO(mkalil): We should eventually consider removing all uses of CoreType.
|
||||
func CoreType(T types.Type) types.Type {
|
||||
U := T.Underlying()
|
||||
if _, ok := U.(*types.Interface); !ok {
|
||||
@@ -34,7 +36,7 @@ func CoreType(T types.Type) types.Type {
|
||||
}
|
||||
|
||||
if identical == len(terms) {
|
||||
// https://go.dev/ref/spec#Core_types
|
||||
// From the deprecated core types spec:
|
||||
// "There is a single type U which is the underlying type of all types in the type set of T"
|
||||
return U
|
||||
}
|
||||
@@ -42,7 +44,7 @@ func CoreType(T types.Type) types.Type {
|
||||
if !ok {
|
||||
return nil // no core type as identical < len(terms) and U is not a channel.
|
||||
}
|
||||
// https://go.dev/ref/spec#Core_types
|
||||
// From the deprecated core types spec:
|
||||
// "the type chan E if T contains only bidirectional channels, or the type chan<- E or
|
||||
// <-chan E depending on the direction of the directional channels present."
|
||||
for chans := identical; chans < len(terms); chans++ {
|
||||
|
||||
+48
@@ -194,3 +194,51 @@ func Imports(pkg *types.Package, path string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ObjectKind returns a description of the object's kind.
|
||||
//
|
||||
// from objectKind in go/types
|
||||
func ObjectKind(obj types.Object) string {
|
||||
switch obj := obj.(type) {
|
||||
case *types.PkgName:
|
||||
return "package name"
|
||||
case *types.Const:
|
||||
return "constant"
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
return "type alias"
|
||||
} else if _, ok := obj.Type().(*types.TypeParam); ok {
|
||||
return "type parameter"
|
||||
} else {
|
||||
return "defined type"
|
||||
}
|
||||
case *types.Var:
|
||||
switch obj.Kind() {
|
||||
case PackageVar:
|
||||
return "package-level variable"
|
||||
case LocalVar:
|
||||
return "local variable"
|
||||
case RecvVar:
|
||||
return "receiver"
|
||||
case ParamVar:
|
||||
return "parameter"
|
||||
case ResultVar:
|
||||
return "result variable"
|
||||
case FieldVar:
|
||||
return "struct field"
|
||||
}
|
||||
case *types.Func:
|
||||
if obj.Signature().Recv() != nil {
|
||||
return "method"
|
||||
} else {
|
||||
return "function"
|
||||
}
|
||||
case *types.Label:
|
||||
return "label"
|
||||
case *types.Builtin:
|
||||
return "built-in function"
|
||||
case *types.Nil:
|
||||
return "untyped nil"
|
||||
}
|
||||
return "unknown symbol"
|
||||
}
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ const (
|
||||
Go1_24 = "go1.24"
|
||||
Go1_25 = "go1.25"
|
||||
Go1_26 = "go1.26"
|
||||
Go1_27 = "go1.27"
|
||||
)
|
||||
|
||||
// Future is an invalid unknown Go version sometime in the future.
|
||||
|
||||
Vendored
+7
-8
@@ -321,7 +321,7 @@ go.uber.org/mock/mockgen
|
||||
go.uber.org/mock/mockgen/model
|
||||
# golang.org/x/arch v0.4.0
|
||||
## explicit; go 1.17
|
||||
# golang.org/x/crypto v0.50.0
|
||||
# golang.org/x/crypto v0.51.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/crypto/blake2b
|
||||
golang.org/x/crypto/blowfish
|
||||
@@ -336,13 +336,13 @@ golang.org/x/crypto/nacl/secretbox
|
||||
golang.org/x/crypto/salsa20/salsa
|
||||
golang.org/x/crypto/ssh
|
||||
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
||||
# golang.org/x/mod v0.34.0
|
||||
# golang.org/x/mod v0.35.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/mod/internal/lazyregexp
|
||||
golang.org/x/mod/modfile
|
||||
golang.org/x/mod/module
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.53.0
|
||||
# golang.org/x/net v0.54.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
@@ -370,7 +370,7 @@ golang.org/x/oauth2/internal
|
||||
# golang.org/x/sync v0.20.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/sync/errgroup
|
||||
# golang.org/x/sys v0.43.0
|
||||
# golang.org/x/sys v0.44.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/execabs
|
||||
@@ -381,10 +381,10 @@ golang.org/x/sys/windows/registry
|
||||
golang.org/x/sys/windows/svc
|
||||
golang.org/x/sys/windows/svc/eventlog
|
||||
golang.org/x/sys/windows/svc/mgr
|
||||
# golang.org/x/term v0.42.0
|
||||
# golang.org/x/term v0.43.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.36.0
|
||||
# golang.org/x/text v0.37.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/text/cases
|
||||
golang.org/x/text/internal
|
||||
@@ -396,7 +396,7 @@ golang.org/x/text/secure/bidirule
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
# golang.org/x/tools v0.43.0
|
||||
# golang.org/x/tools v0.44.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/tools/cover
|
||||
golang.org/x/tools/go/ast/astutil
|
||||
@@ -416,7 +416,6 @@ golang.org/x/tools/internal/gcimporter
|
||||
golang.org/x/tools/internal/gocommand
|
||||
golang.org/x/tools/internal/gopathwalk
|
||||
golang.org/x/tools/internal/imports
|
||||
golang.org/x/tools/internal/modindex
|
||||
golang.org/x/tools/internal/packagesinternal
|
||||
golang.org/x/tools/internal/pkgbits
|
||||
golang.org/x/tools/internal/stdlib
|
||||
|
||||
Reference in New Issue
Block a user