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

This commit is contained in:
João "Pisco" Fernandes
2026-05-13 13:15:15 +01:00
parent 21ca2e225e
commit fbfd76089f
65 changed files with 7303 additions and 26049 deletions
+7 -7
View File
@@ -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
+14 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-4653
View File
File diff suppressed because it is too large Load Diff
-4733
View File
File diff suppressed because it is too large Load Diff
-4959
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-4486
View File
File diff suppressed because it is too large Load Diff
-30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+7 -8
View File
@@ -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