diff --git a/context/context.go b/context/context.go
index cf66309c4a..db1c95fab1 100644
--- a/context/context.go
+++ b/context/context.go
@@ -3,29 +3,31 @@
// license that can be found in the LICENSE file.
// Package context defines the Context type, which carries deadlines,
-// cancelation signals, and other request-scoped values across API boundaries
+// cancellation signals, and other request-scoped values across API boundaries
// and between processes.
// As of Go 1.7 this package is available in the standard library under the
-// name context. https://golang.org/pkg/context.
+// name [context], and migrating to it can be done automatically with [go fix].
//
-// Incoming requests to a server should create a Context, and outgoing calls to
-// servers should accept a Context. The chain of function calls between must
-// propagate the Context, optionally replacing it with a modified copy created
-// using WithDeadline, WithTimeout, WithCancel, or WithValue.
+// Incoming requests to a server should create a [Context], and outgoing
+// calls to servers should accept a Context. The chain of function
+// calls between them must propagate the Context, optionally replacing
+// it with a derived Context created using [WithCancel], [WithDeadline],
+// [WithTimeout], or [WithValue].
//
// Programs that use Contexts should follow these rules to keep interfaces
// consistent across packages and enable static analysis tools to check context
// propagation:
//
// Do not store Contexts inside a struct type; instead, pass a Context
-// explicitly to each function that needs it. The Context should be the first
+// explicitly to each function that needs it. This is discussed further in
+// https://go.dev/blog/context-and-structs. The Context should be the first
// parameter, typically named ctx:
//
// func DoSomething(ctx context.Context, arg Arg) error {
// // ... use ctx ...
// }
//
-// Do not pass a nil Context, even if a function permits it. Pass context.TODO
+// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
// if you are unsure about which Context to use.
//
// Use context Values only for request-scoped data that transits processes and
@@ -34,9 +36,30 @@
// The same Context may be passed to functions running in different goroutines;
// Contexts are safe for simultaneous use by multiple goroutines.
//
-// See http://blog.golang.org/context for example code for a server that uses
+// See https://go.dev/blog/context for example code for a server that uses
// Contexts.
-package context // import "golang.org/x/net/context"
+//
+// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs
+package context
+
+import (
+ "context" // standard library's context, as of Go 1.7
+ "time"
+)
+
+// A Context carries a deadline, a cancellation signal, and other values across
+// API boundaries.
+//
+// Context's methods may be called by multiple goroutines simultaneously.
+type Context = context.Context
+
+// Canceled is the error returned by [Context.Err] when the context is canceled
+// for some reason other than its deadline passing.
+var Canceled = context.Canceled
+
+// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
+// due to its deadline passing.
+var DeadlineExceeded = context.DeadlineExceeded
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
@@ -49,8 +72,73 @@ func Background() Context {
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
-// parameter). TODO is recognized by static analysis tools that determine
-// whether Contexts are propagated correctly in a program.
+// parameter).
func TODO() Context {
return todo
}
+
+var (
+ background = context.Background()
+ todo = context.TODO()
+)
+
+// A CancelFunc tells an operation to abandon its work.
+// A CancelFunc does not wait for the work to stop.
+// A CancelFunc may be called by multiple goroutines simultaneously.
+// After the first call, subsequent calls to a CancelFunc do nothing.
+type CancelFunc = context.CancelFunc
+
+// WithCancel returns a derived context that points to the parent context
+// but has a new Done channel. The returned context's Done channel is closed
+// when the returned cancel function is called or when the parent context's
+// Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete.
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
+ return context.WithCancel(parent)
+}
+
+// WithDeadline returns a derived context that points to the parent context
+// but has the deadline adjusted to be no later than d. If the parent's
+// deadline is already earlier than d, WithDeadline(parent, d) is semantically
+// equivalent to parent. The returned [Context.Done] channel is closed when
+// the deadline expires, when the returned cancel function is called,
+// or when the parent context's Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete.
+func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
+ return context.WithDeadline(parent, d)
+}
+
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this [Context] complete:
+//
+// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
+// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
+// defer cancel() // releases resources if slowOperation completes before timeout elapses
+// return slowOperation(ctx)
+// }
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
+ return context.WithTimeout(parent, timeout)
+}
+
+// WithValue returns a derived context that points to the parent Context.
+// In the derived context, the value associated with key is val.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+//
+// The provided key must be comparable and should not be of type
+// string or any other built-in type to avoid collisions between
+// packages using context. Users of WithValue should define their own
+// types for keys. To avoid allocating when assigning to an
+// interface{}, context keys often have concrete type
+// struct{}. Alternatively, exported context key variables' static
+// type should be a pointer or interface.
+func WithValue(parent Context, key, val interface{}) Context {
+ return context.WithValue(parent, key, val)
+}
diff --git a/context/context_test.go b/context/context_test.go
deleted file mode 100644
index 2cb54edb89..0000000000
--- a/context/context_test.go
+++ /dev/null
@@ -1,583 +0,0 @@
-// Copyright 2014 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.7
-
-package context
-
-import (
- "fmt"
- "math/rand"
- "runtime"
- "strings"
- "sync"
- "testing"
- "time"
-)
-
-// otherContext is a Context that's not one of the types defined in context.go.
-// This lets us test code paths that differ based on the underlying type of the
-// Context.
-type otherContext struct {
- Context
-}
-
-func TestBackground(t *testing.T) {
- c := Background()
- if c == nil {
- t.Fatalf("Background returned nil")
- }
- select {
- case x := <-c.Done():
- t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
- default:
- }
- if got, want := fmt.Sprint(c), "context.Background"; got != want {
- t.Errorf("Background().String() = %q want %q", got, want)
- }
-}
-
-func TestTODO(t *testing.T) {
- c := TODO()
- if c == nil {
- t.Fatalf("TODO returned nil")
- }
- select {
- case x := <-c.Done():
- t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
- default:
- }
- if got, want := fmt.Sprint(c), "context.TODO"; got != want {
- t.Errorf("TODO().String() = %q want %q", got, want)
- }
-}
-
-func TestWithCancel(t *testing.T) {
- c1, cancel := WithCancel(Background())
-
- if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
- t.Errorf("c1.String() = %q want %q", got, want)
- }
-
- o := otherContext{c1}
- c2, _ := WithCancel(o)
- contexts := []Context{c1, o, c2}
-
- for i, c := range contexts {
- if d := c.Done(); d == nil {
- t.Errorf("c[%d].Done() == %v want non-nil", i, d)
- }
- if e := c.Err(); e != nil {
- t.Errorf("c[%d].Err() == %v want nil", i, e)
- }
-
- select {
- case x := <-c.Done():
- t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
- default:
- }
- }
-
- cancel()
- time.Sleep(100 * time.Millisecond) // let cancelation propagate
-
- for i, c := range contexts {
- select {
- case <-c.Done():
- default:
- t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
- }
- if e := c.Err(); e != Canceled {
- t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
- }
- }
-}
-
-func TestParentFinishesChild(t *testing.T) {
- // Context tree:
- // parent -> cancelChild
- // parent -> valueChild -> timerChild
- parent, cancel := WithCancel(Background())
- cancelChild, stop := WithCancel(parent)
- defer stop()
- valueChild := WithValue(parent, "key", "value")
- timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
- defer stop()
-
- select {
- case x := <-parent.Done():
- t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
- case x := <-cancelChild.Done():
- t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
- case x := <-timerChild.Done():
- t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
- case x := <-valueChild.Done():
- t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
- default:
- }
-
- // The parent's children should contain the two cancelable children.
- pc := parent.(*cancelCtx)
- cc := cancelChild.(*cancelCtx)
- tc := timerChild.(*timerCtx)
- pc.mu.Lock()
- if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
- t.Errorf("bad linkage: pc.children = %v, want %v and %v",
- pc.children, cc, tc)
- }
- pc.mu.Unlock()
-
- if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
- t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
- }
- if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
- t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
- }
-
- cancel()
-
- pc.mu.Lock()
- if len(pc.children) != 0 {
- t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
- }
- pc.mu.Unlock()
-
- // parent and children should all be finished.
- check := func(ctx Context, name string) {
- select {
- case <-ctx.Done():
- default:
- t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
- }
- if e := ctx.Err(); e != Canceled {
- t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
- }
- }
- check(parent, "parent")
- check(cancelChild, "cancelChild")
- check(valueChild, "valueChild")
- check(timerChild, "timerChild")
-
- // WithCancel should return a canceled context on a canceled parent.
- precanceledChild := WithValue(parent, "key", "value")
- select {
- case <-precanceledChild.Done():
- default:
- t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
- }
- if e := precanceledChild.Err(); e != Canceled {
- t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
- }
-}
-
-func TestChildFinishesFirst(t *testing.T) {
- cancelable, stop := WithCancel(Background())
- defer stop()
- for _, parent := range []Context{Background(), cancelable} {
- child, cancel := WithCancel(parent)
-
- select {
- case x := <-parent.Done():
- t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
- case x := <-child.Done():
- t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
- default:
- }
-
- cc := child.(*cancelCtx)
- pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
- if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
- t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
- }
-
- if pcok {
- pc.mu.Lock()
- if len(pc.children) != 1 || !pc.children[cc] {
- t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
- }
- pc.mu.Unlock()
- }
-
- cancel()
-
- if pcok {
- pc.mu.Lock()
- if len(pc.children) != 0 {
- t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
- }
- pc.mu.Unlock()
- }
-
- // child should be finished.
- select {
- case <-child.Done():
- default:
- t.Errorf("<-child.Done() blocked, but shouldn't have")
- }
- if e := child.Err(); e != Canceled {
- t.Errorf("child.Err() == %v want %v", e, Canceled)
- }
-
- // parent should not be finished.
- select {
- case x := <-parent.Done():
- t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
- default:
- }
- if e := parent.Err(); e != nil {
- t.Errorf("parent.Err() == %v want nil", e)
- }
- }
-}
-
-func testDeadline(c Context, wait time.Duration, t *testing.T) {
- select {
- case <-time.After(wait):
- t.Fatalf("context should have timed out")
- case <-c.Done():
- }
- if e := c.Err(); e != DeadlineExceeded {
- t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
- }
-}
-
-func TestDeadline(t *testing.T) {
- t.Parallel()
- const timeUnit = 500 * time.Millisecond
- c, _ := WithDeadline(Background(), time.Now().Add(1*timeUnit))
- if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
- t.Errorf("c.String() = %q want prefix %q", got, prefix)
- }
- testDeadline(c, 2*timeUnit, t)
-
- c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
- o := otherContext{c}
- testDeadline(o, 2*timeUnit, t)
-
- c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
- o = otherContext{c}
- c, _ = WithDeadline(o, time.Now().Add(3*timeUnit))
- testDeadline(c, 2*timeUnit, t)
-}
-
-func TestTimeout(t *testing.T) {
- t.Parallel()
- const timeUnit = 500 * time.Millisecond
- c, _ := WithTimeout(Background(), 1*timeUnit)
- if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
- t.Errorf("c.String() = %q want prefix %q", got, prefix)
- }
- testDeadline(c, 2*timeUnit, t)
-
- c, _ = WithTimeout(Background(), 1*timeUnit)
- o := otherContext{c}
- testDeadline(o, 2*timeUnit, t)
-
- c, _ = WithTimeout(Background(), 1*timeUnit)
- o = otherContext{c}
- c, _ = WithTimeout(o, 3*timeUnit)
- testDeadline(c, 2*timeUnit, t)
-}
-
-func TestCanceledTimeout(t *testing.T) {
- t.Parallel()
- const timeUnit = 500 * time.Millisecond
- c, _ := WithTimeout(Background(), 2*timeUnit)
- o := otherContext{c}
- c, cancel := WithTimeout(o, 4*timeUnit)
- cancel()
- time.Sleep(1 * timeUnit) // let cancelation propagate
- select {
- case <-c.Done():
- default:
- t.Errorf("<-c.Done() blocked, but shouldn't have")
- }
- if e := c.Err(); e != Canceled {
- t.Errorf("c.Err() == %v want %v", e, Canceled)
- }
-}
-
-type key1 int
-type key2 int
-
-var k1 = key1(1)
-var k2 = key2(1) // same int as k1, different type
-var k3 = key2(3) // same type as k2, different int
-
-func TestValues(t *testing.T) {
- check := func(c Context, nm, v1, v2, v3 string) {
- if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
- t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
- }
- if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
- t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
- }
- if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
- t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
- }
- }
-
- c0 := Background()
- check(c0, "c0", "", "", "")
-
- c1 := WithValue(Background(), k1, "c1k1")
- check(c1, "c1", "c1k1", "", "")
-
- if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
- t.Errorf("c.String() = %q want %q", got, want)
- }
-
- c2 := WithValue(c1, k2, "c2k2")
- check(c2, "c2", "c1k1", "c2k2", "")
-
- c3 := WithValue(c2, k3, "c3k3")
- check(c3, "c2", "c1k1", "c2k2", "c3k3")
-
- c4 := WithValue(c3, k1, nil)
- check(c4, "c4", "", "c2k2", "c3k3")
-
- o0 := otherContext{Background()}
- check(o0, "o0", "", "", "")
-
- o1 := otherContext{WithValue(Background(), k1, "c1k1")}
- check(o1, "o1", "c1k1", "", "")
-
- o2 := WithValue(o1, k2, "o2k2")
- check(o2, "o2", "c1k1", "o2k2", "")
-
- o3 := otherContext{c4}
- check(o3, "o3", "", "c2k2", "c3k3")
-
- o4 := WithValue(o3, k3, nil)
- check(o4, "o4", "", "c2k2", "")
-}
-
-func TestAllocs(t *testing.T) {
- bg := Background()
- for _, test := range []struct {
- desc string
- f func()
- limit float64
- gccgoLimit float64
- }{
- {
- desc: "Background()",
- f: func() { Background() },
- limit: 0,
- gccgoLimit: 0,
- },
- {
- desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
- f: func() {
- c := WithValue(bg, k1, nil)
- c.Value(k1)
- },
- limit: 3,
- gccgoLimit: 3,
- },
- {
- desc: "WithTimeout(bg, 15*time.Millisecond)",
- f: func() {
- c, _ := WithTimeout(bg, 15*time.Millisecond)
- <-c.Done()
- },
- limit: 8,
- gccgoLimit: 16,
- },
- {
- desc: "WithCancel(bg)",
- f: func() {
- c, cancel := WithCancel(bg)
- cancel()
- <-c.Done()
- },
- limit: 5,
- gccgoLimit: 8,
- },
- {
- desc: "WithTimeout(bg, 100*time.Millisecond)",
- f: func() {
- c, cancel := WithTimeout(bg, 100*time.Millisecond)
- cancel()
- <-c.Done()
- },
- limit: 8,
- gccgoLimit: 25,
- },
- } {
- limit := test.limit
- if runtime.Compiler == "gccgo" {
- // gccgo does not yet do escape analysis.
- // TODO(iant): Remove this when gccgo does do escape analysis.
- limit = test.gccgoLimit
- }
- if n := testing.AllocsPerRun(100, test.f); n > limit {
- t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
- }
- }
-}
-
-func TestSimultaneousCancels(t *testing.T) {
- root, cancel := WithCancel(Background())
- m := map[Context]CancelFunc{root: cancel}
- q := []Context{root}
- // Create a tree of contexts.
- for len(q) != 0 && len(m) < 100 {
- parent := q[0]
- q = q[1:]
- for i := 0; i < 4; i++ {
- ctx, cancel := WithCancel(parent)
- m[ctx] = cancel
- q = append(q, ctx)
- }
- }
- // Start all the cancels in a random order.
- var wg sync.WaitGroup
- wg.Add(len(m))
- for _, cancel := range m {
- go func(cancel CancelFunc) {
- cancel()
- wg.Done()
- }(cancel)
- }
- // Wait on all the contexts in a random order.
- for ctx := range m {
- select {
- case <-ctx.Done():
- case <-time.After(1 * time.Second):
- buf := make([]byte, 10<<10)
- n := runtime.Stack(buf, true)
- t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
- }
- }
- // Wait for all the cancel functions to return.
- done := make(chan struct{})
- go func() {
- wg.Wait()
- close(done)
- }()
- select {
- case <-done:
- case <-time.After(1 * time.Second):
- buf := make([]byte, 10<<10)
- n := runtime.Stack(buf, true)
- t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
- }
-}
-
-func TestInterlockedCancels(t *testing.T) {
- parent, cancelParent := WithCancel(Background())
- child, cancelChild := WithCancel(parent)
- go func() {
- parent.Done()
- cancelChild()
- }()
- cancelParent()
- select {
- case <-child.Done():
- case <-time.After(1 * time.Second):
- buf := make([]byte, 10<<10)
- n := runtime.Stack(buf, true)
- t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
- }
-}
-
-func TestLayersCancel(t *testing.T) {
- testLayers(t, time.Now().UnixNano(), false)
-}
-
-func TestLayersTimeout(t *testing.T) {
- testLayers(t, time.Now().UnixNano(), true)
-}
-
-func testLayers(t *testing.T, seed int64, testTimeout bool) {
- rand.Seed(seed)
- errorf := func(format string, a ...interface{}) {
- t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
- }
- const (
- timeout = 200 * time.Millisecond
- minLayers = 30
- )
- type value int
- var (
- vals []*value
- cancels []CancelFunc
- numTimers int
- ctx = Background()
- )
- for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
- switch rand.Intn(3) {
- case 0:
- v := new(value)
- ctx = WithValue(ctx, v, v)
- vals = append(vals, v)
- case 1:
- var cancel CancelFunc
- ctx, cancel = WithCancel(ctx)
- cancels = append(cancels, cancel)
- case 2:
- var cancel CancelFunc
- ctx, cancel = WithTimeout(ctx, timeout)
- cancels = append(cancels, cancel)
- numTimers++
- }
- }
- checkValues := func(when string) {
- for _, key := range vals {
- if val := ctx.Value(key).(*value); key != val {
- errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
- }
- }
- }
- select {
- case <-ctx.Done():
- errorf("ctx should not be canceled yet")
- default:
- }
- if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
- t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
- }
- t.Log(ctx)
- checkValues("before cancel")
- if testTimeout {
- select {
- case <-ctx.Done():
- case <-time.After(timeout + 100*time.Millisecond):
- errorf("ctx should have timed out")
- }
- checkValues("after timeout")
- } else {
- cancel := cancels[rand.Intn(len(cancels))]
- cancel()
- select {
- case <-ctx.Done():
- default:
- errorf("ctx should be canceled")
- }
- checkValues("after cancel")
- }
-}
-
-func TestCancelRemoves(t *testing.T) {
- checkChildren := func(when string, ctx Context, want int) {
- if got := len(ctx.(*cancelCtx).children); got != want {
- t.Errorf("%s: context has %d children, want %d", when, got, want)
- }
- }
-
- ctx, _ := WithCancel(Background())
- checkChildren("after creation", ctx, 0)
- _, cancel := WithCancel(ctx)
- checkChildren("with WithCancel child ", ctx, 1)
- cancel()
- checkChildren("after cancelling WithCancel child", ctx, 0)
-
- ctx, _ = WithCancel(Background())
- checkChildren("after creation", ctx, 0)
- _, cancel = WithTimeout(ctx, 60*time.Minute)
- checkChildren("with WithTimeout child ", ctx, 1)
- cancel()
- checkChildren("after cancelling WithTimeout child", ctx, 0)
-}
diff --git a/context/ctxhttp/ctxhttp.go b/context/ctxhttp/ctxhttp.go
index 37dc0cfdb5..e0df203cea 100644
--- a/context/ctxhttp/ctxhttp.go
+++ b/context/ctxhttp/ctxhttp.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
-package ctxhttp // import "golang.org/x/net/context/ctxhttp"
+package ctxhttp
import (
"context"
diff --git a/context/go17.go b/context/go17.go
deleted file mode 100644
index 0c1b867937..0000000000
--- a/context/go17.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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.7
-
-package context
-
-import (
- "context" // standard library's context, as of Go 1.7
- "time"
-)
-
-var (
- todo = context.TODO()
- background = context.Background()
-)
-
-// Canceled is the error returned by Context.Err when the context is canceled.
-var Canceled = context.Canceled
-
-// DeadlineExceeded is the error returned by Context.Err when the context's
-// deadline passes.
-var DeadlineExceeded = context.DeadlineExceeded
-
-// WithCancel returns a copy of parent with a new Done channel. The returned
-// context's Done channel is closed when the returned cancel function is called
-// or when the parent context's Done channel is closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
- ctx, f := context.WithCancel(parent)
- return ctx, f
-}
-
-// WithDeadline returns a copy of the parent context with the deadline adjusted
-// to be no later than d. If the parent's deadline is already earlier than d,
-// WithDeadline(parent, d) is semantically equivalent to parent. The returned
-// context's Done channel is closed when the deadline expires, when the returned
-// cancel function is called, or when the parent context's Done channel is
-// closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
- ctx, f := context.WithDeadline(parent, deadline)
- return ctx, f
-}
-
-// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete:
-//
-// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
-// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
-// defer cancel() // releases resources if slowOperation completes before timeout elapses
-// return slowOperation(ctx)
-// }
-func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
- return WithDeadline(parent, time.Now().Add(timeout))
-}
-
-// WithValue returns a copy of parent in which the value associated with key is
-// val.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-func WithValue(parent Context, key interface{}, val interface{}) Context {
- return context.WithValue(parent, key, val)
-}
diff --git a/context/go19.go b/context/go19.go
deleted file mode 100644
index e31e35a904..0000000000
--- a/context/go19.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 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.9
-
-package context
-
-import "context" // standard library's context, as of Go 1.7
-
-// A Context carries a deadline, a cancelation signal, and other values across
-// API boundaries.
-//
-// Context's methods may be called by multiple goroutines simultaneously.
-type Context = context.Context
-
-// A CancelFunc tells an operation to abandon its work.
-// A CancelFunc does not wait for the work to stop.
-// After the first call, subsequent calls to a CancelFunc do nothing.
-type CancelFunc = context.CancelFunc
diff --git a/context/pre_go17.go b/context/pre_go17.go
deleted file mode 100644
index 065ff3dfa5..0000000000
--- a/context/pre_go17.go
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright 2014 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.7
-
-package context
-
-import (
- "errors"
- "fmt"
- "sync"
- "time"
-)
-
-// An emptyCtx is never canceled, has no values, and has no deadline. It is not
-// struct{}, since vars of this type must have distinct addresses.
-type emptyCtx int
-
-func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
- return
-}
-
-func (*emptyCtx) Done() <-chan struct{} {
- return nil
-}
-
-func (*emptyCtx) Err() error {
- return nil
-}
-
-func (*emptyCtx) Value(key interface{}) interface{} {
- return nil
-}
-
-func (e *emptyCtx) String() string {
- switch e {
- case background:
- return "context.Background"
- case todo:
- return "context.TODO"
- }
- return "unknown empty Context"
-}
-
-var (
- background = new(emptyCtx)
- todo = new(emptyCtx)
-)
-
-// Canceled is the error returned by Context.Err when the context is canceled.
-var Canceled = errors.New("context canceled")
-
-// DeadlineExceeded is the error returned by Context.Err when the context's
-// deadline passes.
-var DeadlineExceeded = errors.New("context deadline exceeded")
-
-// WithCancel returns a copy of parent with a new Done channel. The returned
-// context's Done channel is closed when the returned cancel function is called
-// or when the parent context's Done channel is closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
- c := newCancelCtx(parent)
- propagateCancel(parent, c)
- return c, func() { c.cancel(true, Canceled) }
-}
-
-// newCancelCtx returns an initialized cancelCtx.
-func newCancelCtx(parent Context) *cancelCtx {
- return &cancelCtx{
- Context: parent,
- done: make(chan struct{}),
- }
-}
-
-// propagateCancel arranges for child to be canceled when parent is.
-func propagateCancel(parent Context, child canceler) {
- if parent.Done() == nil {
- return // parent is never canceled
- }
- if p, ok := parentCancelCtx(parent); ok {
- p.mu.Lock()
- if p.err != nil {
- // parent has already been canceled
- child.cancel(false, p.err)
- } else {
- if p.children == nil {
- p.children = make(map[canceler]bool)
- }
- p.children[child] = true
- }
- p.mu.Unlock()
- } else {
- go func() {
- select {
- case <-parent.Done():
- child.cancel(false, parent.Err())
- case <-child.Done():
- }
- }()
- }
-}
-
-// parentCancelCtx follows a chain of parent references until it finds a
-// *cancelCtx. This function understands how each of the concrete types in this
-// package represents its parent.
-func parentCancelCtx(parent Context) (*cancelCtx, bool) {
- for {
- switch c := parent.(type) {
- case *cancelCtx:
- return c, true
- case *timerCtx:
- return c.cancelCtx, true
- case *valueCtx:
- parent = c.Context
- default:
- return nil, false
- }
- }
-}
-
-// removeChild removes a context from its parent.
-func removeChild(parent Context, child canceler) {
- p, ok := parentCancelCtx(parent)
- if !ok {
- return
- }
- p.mu.Lock()
- if p.children != nil {
- delete(p.children, child)
- }
- p.mu.Unlock()
-}
-
-// A canceler is a context type that can be canceled directly. The
-// implementations are *cancelCtx and *timerCtx.
-type canceler interface {
- cancel(removeFromParent bool, err error)
- Done() <-chan struct{}
-}
-
-// A cancelCtx can be canceled. When canceled, it also cancels any children
-// that implement canceler.
-type cancelCtx struct {
- Context
-
- done chan struct{} // closed by the first cancel call.
-
- mu sync.Mutex
- children map[canceler]bool // set to nil by the first cancel call
- err error // set to non-nil by the first cancel call
-}
-
-func (c *cancelCtx) Done() <-chan struct{} {
- return c.done
-}
-
-func (c *cancelCtx) Err() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.err
-}
-
-func (c *cancelCtx) String() string {
- return fmt.Sprintf("%v.WithCancel", c.Context)
-}
-
-// cancel closes c.done, cancels each of c's children, and, if
-// removeFromParent is true, removes c from its parent's children.
-func (c *cancelCtx) cancel(removeFromParent bool, err error) {
- if err == nil {
- panic("context: internal error: missing cancel error")
- }
- c.mu.Lock()
- if c.err != nil {
- c.mu.Unlock()
- return // already canceled
- }
- c.err = err
- close(c.done)
- for child := range c.children {
- // NOTE: acquiring the child's lock while holding parent's lock.
- child.cancel(false, err)
- }
- c.children = nil
- c.mu.Unlock()
-
- if removeFromParent {
- removeChild(c.Context, c)
- }
-}
-
-// WithDeadline returns a copy of the parent context with the deadline adjusted
-// to be no later than d. If the parent's deadline is already earlier than d,
-// WithDeadline(parent, d) is semantically equivalent to parent. The returned
-// context's Done channel is closed when the deadline expires, when the returned
-// cancel function is called, or when the parent context's Done channel is
-// closed, whichever happens first.
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete.
-func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
- if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
- // The current deadline is already sooner than the new one.
- return WithCancel(parent)
- }
- c := &timerCtx{
- cancelCtx: newCancelCtx(parent),
- deadline: deadline,
- }
- propagateCancel(parent, c)
- d := deadline.Sub(time.Now())
- if d <= 0 {
- c.cancel(true, DeadlineExceeded) // deadline has already passed
- return c, func() { c.cancel(true, Canceled) }
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.err == nil {
- c.timer = time.AfterFunc(d, func() {
- c.cancel(true, DeadlineExceeded)
- })
- }
- return c, func() { c.cancel(true, Canceled) }
-}
-
-// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
-// implement Done and Err. It implements cancel by stopping its timer then
-// delegating to cancelCtx.cancel.
-type timerCtx struct {
- *cancelCtx
- timer *time.Timer // Under cancelCtx.mu.
-
- deadline time.Time
-}
-
-func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
- return c.deadline, true
-}
-
-func (c *timerCtx) String() string {
- return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
-}
-
-func (c *timerCtx) cancel(removeFromParent bool, err error) {
- c.cancelCtx.cancel(false, err)
- if removeFromParent {
- // Remove this timerCtx from its parent cancelCtx's children.
- removeChild(c.cancelCtx.Context, c)
- }
- c.mu.Lock()
- if c.timer != nil {
- c.timer.Stop()
- c.timer = nil
- }
- c.mu.Unlock()
-}
-
-// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-//
-// Canceling this context releases resources associated with it, so code should
-// call cancel as soon as the operations running in this Context complete:
-//
-// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
-// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
-// defer cancel() // releases resources if slowOperation completes before timeout elapses
-// return slowOperation(ctx)
-// }
-func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
- return WithDeadline(parent, time.Now().Add(timeout))
-}
-
-// WithValue returns a copy of parent in which the value associated with key is
-// val.
-//
-// Use context Values only for request-scoped data that transits processes and
-// APIs, not for passing optional parameters to functions.
-func WithValue(parent Context, key interface{}, val interface{}) Context {
- return &valueCtx{parent, key, val}
-}
-
-// A valueCtx carries a key-value pair. It implements Value for that key and
-// delegates all other calls to the embedded Context.
-type valueCtx struct {
- Context
- key, val interface{}
-}
-
-func (c *valueCtx) String() string {
- return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
-}
-
-func (c *valueCtx) Value(key interface{}) interface{} {
- if c.key == key {
- return c.val
- }
- return c.Context.Value(key)
-}
diff --git a/context/pre_go19.go b/context/pre_go19.go
deleted file mode 100644
index ec5a638033..0000000000
--- a/context/pre_go19.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2014 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.9
-
-package context
-
-import "time"
-
-// A Context carries a deadline, a cancelation signal, and other values across
-// API boundaries.
-//
-// Context's methods may be called by multiple goroutines simultaneously.
-type Context interface {
- // Deadline returns the time when work done on behalf of this context
- // should be canceled. Deadline returns ok==false when no deadline is
- // set. Successive calls to Deadline return the same results.
- Deadline() (deadline time.Time, ok bool)
-
- // Done returns a channel that's closed when work done on behalf of this
- // context should be canceled. Done may return nil if this context can
- // never be canceled. Successive calls to Done return the same value.
- //
- // WithCancel arranges for Done to be closed when cancel is called;
- // WithDeadline arranges for Done to be closed when the deadline
- // expires; WithTimeout arranges for Done to be closed when the timeout
- // elapses.
- //
- // Done is provided for use in select statements:
- //
- // // Stream generates values with DoSomething and sends them to out
- // // until DoSomething returns an error or ctx.Done is closed.
- // func Stream(ctx context.Context, out chan<- Value) error {
- // for {
- // v, err := DoSomething(ctx)
- // if err != nil {
- // return err
- // }
- // select {
- // case <-ctx.Done():
- // return ctx.Err()
- // case out <- v:
- // }
- // }
- // }
- //
- // See http://blog.golang.org/pipelines for more examples of how to use
- // a Done channel for cancelation.
- Done() <-chan struct{}
-
- // Err returns a non-nil error value after Done is closed. Err returns
- // Canceled if the context was canceled or DeadlineExceeded if the
- // context's deadline passed. No other values for Err are defined.
- // After Done is closed, successive calls to Err return the same value.
- Err() error
-
- // Value returns the value associated with this context for key, or nil
- // if no value is associated with key. Successive calls to Value with
- // the same key returns the same result.
- //
- // Use context values only for request-scoped data that transits
- // processes and API boundaries, not for passing optional parameters to
- // functions.
- //
- // A key identifies a specific value in a Context. Functions that wish
- // to store values in Context typically allocate a key in a global
- // variable then use that key as the argument to context.WithValue and
- // Context.Value. A key can be any type that supports equality;
- // packages should define keys as an unexported type to avoid
- // collisions.
- //
- // Packages that define a Context key should provide type-safe accessors
- // for the values stores using that key:
- //
- // // Package user defines a User type that's stored in Contexts.
- // package user
- //
- // import "golang.org/x/net/context"
- //
- // // User is the type of value stored in the Contexts.
- // type User struct {...}
- //
- // // key is an unexported type for keys defined in this package.
- // // This prevents collisions with keys defined in other packages.
- // type key int
- //
- // // userKey is the key for user.User values in Contexts. It is
- // // unexported; clients use user.NewContext and user.FromContext
- // // instead of using this key directly.
- // var userKey key = 0
- //
- // // NewContext returns a new Context that carries value u.
- // func NewContext(ctx context.Context, u *User) context.Context {
- // return context.WithValue(ctx, userKey, u)
- // }
- //
- // // FromContext returns the User value stored in ctx, if any.
- // func FromContext(ctx context.Context) (*User, bool) {
- // u, ok := ctx.Value(userKey).(*User)
- // return u, ok
- // }
- Value(key interface{}) interface{}
-}
-
-// A CancelFunc tells an operation to abandon its work.
-// A CancelFunc does not wait for the work to stop.
-// After the first call, subsequent calls to a CancelFunc do nothing.
-type CancelFunc func()
diff --git a/context/withtimeout_test.go b/context/withtimeout_test.go
deleted file mode 100644
index e6f56691d1..0000000000
--- a/context/withtimeout_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2014 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 context_test
-
-import (
- "fmt"
- "time"
-
- "golang.org/x/net/context"
-)
-
-// This example passes a context with a timeout to tell a blocking function that
-// it should abandon its work after the timeout elapses.
-func ExampleWithTimeout() {
- // Pass a context with a timeout to tell a blocking function that it
- // should abandon its work after the timeout elapses.
- ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
- defer cancel()
-
- select {
- case <-time.After(1 * time.Second):
- fmt.Println("overslept")
- case <-ctx.Done():
- fmt.Println(ctx.Err()) // prints "context deadline exceeded"
- }
-
- // Output:
- // context deadline exceeded
-}
diff --git a/go.mod b/go.mod
index 26852e7822..7ad6254fd4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,10 @@
module golang.org/x/net
-go 1.18
+go 1.23.0
require (
- golang.org/x/crypto v0.31.0
- golang.org/x/sys v0.28.0
- golang.org/x/term v0.27.0
- golang.org/x/text v0.21.0
+ golang.org/x/crypto v0.36.0
+ golang.org/x/sys v0.31.0
+ golang.org/x/term v0.30.0
+ golang.org/x/text v0.23.0
)
diff --git a/go.sum b/go.sum
index 43b5f03464..27aba5f819 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
diff --git a/html/atom/gen.go b/html/atom/gen.go
index 1e249d163c..42b84cbf10 100644
--- a/html/atom/gen.go
+++ b/html/atom/gen.go
@@ -540,6 +540,7 @@ var attributes = []string{
"scope",
"scoped",
"seamless",
+ "search",
"selected",
"shape",
"size",
diff --git a/html/atom/table.go b/html/atom/table.go
index 2a938864cb..b460e6f722 100644
--- a/html/atom/table.go
+++ b/html/atom/table.go
@@ -11,23 +11,23 @@ const (
AcceptCharset Atom = 0x1a0e
Accesskey Atom = 0x2c09
Acronym Atom = 0xaa07
- Action Atom = 0x27206
- Address Atom = 0x6f307
+ Action Atom = 0x26506
+ Address Atom = 0x6f107
Align Atom = 0xb105
- Allowfullscreen Atom = 0x2080f
+ Allowfullscreen Atom = 0x3280f
Allowpaymentrequest Atom = 0xc113
Allowusermedia Atom = 0xdd0e
Alt Atom = 0xf303
Annotation Atom = 0x1c90a
AnnotationXml Atom = 0x1c90e
- Applet Atom = 0x31906
- Area Atom = 0x35604
- Article Atom = 0x3fc07
+ Applet Atom = 0x30806
+ Area Atom = 0x35004
+ Article Atom = 0x3f607
As Atom = 0x3c02
Aside Atom = 0x10705
Async Atom = 0xff05
Audio Atom = 0x11505
- Autocomplete Atom = 0x2780c
+ Autocomplete Atom = 0x26b0c
Autofocus Atom = 0x12109
Autoplay Atom = 0x13c08
B Atom = 0x101
@@ -43,34 +43,34 @@ const (
Br Atom = 0x202
Button Atom = 0x19106
Canvas Atom = 0x10306
- Caption Atom = 0x23107
- Center Atom = 0x22006
- Challenge Atom = 0x29b09
+ Caption Atom = 0x22407
+ Center Atom = 0x21306
+ Challenge Atom = 0x28e09
Charset Atom = 0x2107
- Checked Atom = 0x47907
+ Checked Atom = 0x5b507
Cite Atom = 0x19c04
- Class Atom = 0x56405
- Code Atom = 0x5c504
+ Class Atom = 0x55805
+ Code Atom = 0x5ee04
Col Atom = 0x1ab03
Colgroup Atom = 0x1ab08
Color Atom = 0x1bf05
Cols Atom = 0x1c404
Colspan Atom = 0x1c407
Command Atom = 0x1d707
- Content Atom = 0x58b07
- Contenteditable Atom = 0x58b0f
- Contextmenu Atom = 0x3800b
+ Content Atom = 0x57b07
+ Contenteditable Atom = 0x57b0f
+ Contextmenu Atom = 0x37a0b
Controls Atom = 0x1de08
- Coords Atom = 0x1ea06
- Crossorigin Atom = 0x1fb0b
- Data Atom = 0x4a504
- Datalist Atom = 0x4a508
- Datetime Atom = 0x2b808
- Dd Atom = 0x2d702
+ Coords Atom = 0x1f006
+ Crossorigin Atom = 0x1fa0b
+ Data Atom = 0x49904
+ Datalist Atom = 0x49908
+ Datetime Atom = 0x2ab08
+ Dd Atom = 0x2bf02
Default Atom = 0x10a07
- Defer Atom = 0x5c705
- Del Atom = 0x45203
- Desc Atom = 0x56104
+ Defer Atom = 0x5f005
+ Del Atom = 0x44c03
+ Desc Atom = 0x55504
Details Atom = 0x7207
Dfn Atom = 0x8703
Dialog Atom = 0xbb06
@@ -78,106 +78,106 @@ const (
Dirname Atom = 0x9307
Disabled Atom = 0x16408
Div Atom = 0x16b03
- Dl Atom = 0x5e602
- Download Atom = 0x46308
+ Dl Atom = 0x5d602
+ Download Atom = 0x45d08
Draggable Atom = 0x17a09
- Dropzone Atom = 0x40508
- Dt Atom = 0x64b02
+ Dropzone Atom = 0x3ff08
+ Dt Atom = 0x64002
Em Atom = 0x6e02
Embed Atom = 0x6e05
- Enctype Atom = 0x28d07
- Face Atom = 0x21e04
- Fieldset Atom = 0x22608
- Figcaption Atom = 0x22e0a
- Figure Atom = 0x24806
+ Enctype Atom = 0x28007
+ Face Atom = 0x21104
+ Fieldset Atom = 0x21908
+ Figcaption Atom = 0x2210a
+ Figure Atom = 0x23b06
Font Atom = 0x3f04
Footer Atom = 0xf606
- For Atom = 0x25403
- ForeignObject Atom = 0x2540d
- Foreignobject Atom = 0x2610d
- Form Atom = 0x26e04
- Formaction Atom = 0x26e0a
- Formenctype Atom = 0x2890b
- Formmethod Atom = 0x2a40a
- Formnovalidate Atom = 0x2ae0e
- Formtarget Atom = 0x2c00a
+ For Atom = 0x24703
+ ForeignObject Atom = 0x2470d
+ Foreignobject Atom = 0x2540d
+ Form Atom = 0x26104
+ Formaction Atom = 0x2610a
+ Formenctype Atom = 0x27c0b
+ Formmethod Atom = 0x2970a
+ Formnovalidate Atom = 0x2a10e
+ Formtarget Atom = 0x2b30a
Frame Atom = 0x8b05
Frameset Atom = 0x8b08
H1 Atom = 0x15c02
- H2 Atom = 0x2de02
- H3 Atom = 0x30d02
- H4 Atom = 0x34502
- H5 Atom = 0x34f02
- H6 Atom = 0x64d02
- Head Atom = 0x33104
- Header Atom = 0x33106
- Headers Atom = 0x33107
+ H2 Atom = 0x56102
+ H3 Atom = 0x2cd02
+ H4 Atom = 0x2fc02
+ H5 Atom = 0x33f02
+ H6 Atom = 0x34902
+ Head Atom = 0x32004
+ Header Atom = 0x32006
+ Headers Atom = 0x32007
Height Atom = 0x5206
- Hgroup Atom = 0x2ca06
- Hidden Atom = 0x2d506
- High Atom = 0x2db04
+ Hgroup Atom = 0x64206
+ Hidden Atom = 0x2bd06
+ High Atom = 0x2ca04
Hr Atom = 0x15702
- Href Atom = 0x2e004
- Hreflang Atom = 0x2e008
+ Href Atom = 0x2cf04
+ Hreflang Atom = 0x2cf08
Html Atom = 0x5604
- HttpEquiv Atom = 0x2e80a
+ HttpEquiv Atom = 0x2d70a
I Atom = 0x601
- Icon Atom = 0x58a04
+ Icon Atom = 0x57a04
Id Atom = 0x10902
- Iframe Atom = 0x2fc06
- Image Atom = 0x30205
- Img Atom = 0x30703
- Input Atom = 0x44b05
- Inputmode Atom = 0x44b09
- Ins Atom = 0x20403
- Integrity Atom = 0x23f09
+ Iframe Atom = 0x2eb06
+ Image Atom = 0x2f105
+ Img Atom = 0x2f603
+ Input Atom = 0x44505
+ Inputmode Atom = 0x44509
+ Ins Atom = 0x20303
+ Integrity Atom = 0x23209
Is Atom = 0x16502
- Isindex Atom = 0x30f07
- Ismap Atom = 0x31605
- Itemid Atom = 0x38b06
+ Isindex Atom = 0x2fe07
+ Ismap Atom = 0x30505
+ Itemid Atom = 0x38506
Itemprop Atom = 0x19d08
- Itemref Atom = 0x3cd07
- Itemscope Atom = 0x67109
- Itemtype Atom = 0x31f08
+ Itemref Atom = 0x3c707
+ Itemscope Atom = 0x66f09
+ Itemtype Atom = 0x30e08
Kbd Atom = 0xb903
Keygen Atom = 0x3206
Keytype Atom = 0xd607
Kind Atom = 0x17704
Label Atom = 0x5905
- Lang Atom = 0x2e404
+ Lang Atom = 0x2d304
Legend Atom = 0x18106
Li Atom = 0xb202
Link Atom = 0x17404
- List Atom = 0x4a904
- Listing Atom = 0x4a907
+ List Atom = 0x49d04
+ Listing Atom = 0x49d07
Loop Atom = 0x5d04
Low Atom = 0xc303
Main Atom = 0x1004
Malignmark Atom = 0xb00a
- Manifest Atom = 0x6d708
- Map Atom = 0x31803
+ Manifest Atom = 0x6d508
+ Map Atom = 0x30703
Mark Atom = 0xb604
- Marquee Atom = 0x32707
- Math Atom = 0x32e04
- Max Atom = 0x33d03
- Maxlength Atom = 0x33d09
+ Marquee Atom = 0x31607
+ Math Atom = 0x31d04
+ Max Atom = 0x33703
+ Maxlength Atom = 0x33709
Media Atom = 0xe605
Mediagroup Atom = 0xe60a
- Menu Atom = 0x38704
- Menuitem Atom = 0x38708
- Meta Atom = 0x4b804
+ Menu Atom = 0x38104
+ Menuitem Atom = 0x38108
+ Meta Atom = 0x4ac04
Meter Atom = 0x9805
- Method Atom = 0x2a806
- Mglyph Atom = 0x30806
- Mi Atom = 0x34702
- Min Atom = 0x34703
- Minlength Atom = 0x34709
- Mn Atom = 0x2b102
+ Method Atom = 0x29b06
+ Mglyph Atom = 0x2f706
+ Mi Atom = 0x34102
+ Min Atom = 0x34103
+ Minlength Atom = 0x34109
+ Mn Atom = 0x2a402
Mo Atom = 0xa402
- Ms Atom = 0x67402
- Mtext Atom = 0x35105
- Multiple Atom = 0x35f08
- Muted Atom = 0x36705
+ Ms Atom = 0x67202
+ Mtext Atom = 0x34b05
+ Multiple Atom = 0x35908
+ Muted Atom = 0x36105
Name Atom = 0x9604
Nav Atom = 0x1303
Nobr Atom = 0x3704
@@ -185,101 +185,101 @@ const (
Noframes Atom = 0x8908
Nomodule Atom = 0xa208
Nonce Atom = 0x1a605
- Noscript Atom = 0x21608
- Novalidate Atom = 0x2b20a
- Object Atom = 0x26806
+ Noscript Atom = 0x2c208
+ Novalidate Atom = 0x2a50a
+ Object Atom = 0x25b06
Ol Atom = 0x13702
Onabort Atom = 0x19507
- Onafterprint Atom = 0x2360c
- Onautocomplete Atom = 0x2760e
- Onautocompleteerror Atom = 0x27613
- Onauxclick Atom = 0x61f0a
- Onbeforeprint Atom = 0x69e0d
- Onbeforeunload Atom = 0x6e70e
- Onblur Atom = 0x56d06
+ Onafterprint Atom = 0x2290c
+ Onautocomplete Atom = 0x2690e
+ Onautocompleteerror Atom = 0x26913
+ Onauxclick Atom = 0x6140a
+ Onbeforeprint Atom = 0x69c0d
+ Onbeforeunload Atom = 0x6e50e
+ Onblur Atom = 0x1ea06
Oncancel Atom = 0x11908
Oncanplay Atom = 0x14d09
Oncanplaythrough Atom = 0x14d10
- Onchange Atom = 0x41b08
- Onclick Atom = 0x2f507
- Onclose Atom = 0x36c07
- Oncontextmenu Atom = 0x37e0d
- Oncopy Atom = 0x39106
- Oncuechange Atom = 0x3970b
- Oncut Atom = 0x3a205
- Ondblclick Atom = 0x3a70a
- Ondrag Atom = 0x3b106
- Ondragend Atom = 0x3b109
- Ondragenter Atom = 0x3ba0b
- Ondragexit Atom = 0x3c50a
- Ondragleave Atom = 0x3df0b
- Ondragover Atom = 0x3ea0a
- Ondragstart Atom = 0x3f40b
- Ondrop Atom = 0x40306
- Ondurationchange Atom = 0x41310
- Onemptied Atom = 0x40a09
- Onended Atom = 0x42307
- Onerror Atom = 0x42a07
- Onfocus Atom = 0x43107
- Onhashchange Atom = 0x43d0c
- Oninput Atom = 0x44907
- Oninvalid Atom = 0x45509
- Onkeydown Atom = 0x45e09
- Onkeypress Atom = 0x46b0a
- Onkeyup Atom = 0x48007
- Onlanguagechange Atom = 0x48d10
- Onload Atom = 0x49d06
- Onloadeddata Atom = 0x49d0c
- Onloadedmetadata Atom = 0x4b010
- Onloadend Atom = 0x4c609
- Onloadstart Atom = 0x4cf0b
- Onmessage Atom = 0x4da09
- Onmessageerror Atom = 0x4da0e
- Onmousedown Atom = 0x4e80b
- Onmouseenter Atom = 0x4f30c
- Onmouseleave Atom = 0x4ff0c
- Onmousemove Atom = 0x50b0b
- Onmouseout Atom = 0x5160a
- Onmouseover Atom = 0x5230b
- Onmouseup Atom = 0x52e09
- Onmousewheel Atom = 0x53c0c
- Onoffline Atom = 0x54809
- Ononline Atom = 0x55108
- Onpagehide Atom = 0x5590a
- Onpageshow Atom = 0x5730a
- Onpaste Atom = 0x57f07
- Onpause Atom = 0x59a07
- Onplay Atom = 0x5a406
- Onplaying Atom = 0x5a409
- Onpopstate Atom = 0x5ad0a
- Onprogress Atom = 0x5b70a
- Onratechange Atom = 0x5cc0c
- Onrejectionhandled Atom = 0x5d812
- Onreset Atom = 0x5ea07
- Onresize Atom = 0x5f108
- Onscroll Atom = 0x60008
- Onsecuritypolicyviolation Atom = 0x60819
- Onseeked Atom = 0x62908
- Onseeking Atom = 0x63109
- Onselect Atom = 0x63a08
- Onshow Atom = 0x64406
- Onsort Atom = 0x64f06
- Onstalled Atom = 0x65909
- Onstorage Atom = 0x66209
- Onsubmit Atom = 0x66b08
- Onsuspend Atom = 0x67b09
+ Onchange Atom = 0x41508
+ Onclick Atom = 0x2e407
+ Onclose Atom = 0x36607
+ Oncontextmenu Atom = 0x3780d
+ Oncopy Atom = 0x38b06
+ Oncuechange Atom = 0x3910b
+ Oncut Atom = 0x39c05
+ Ondblclick Atom = 0x3a10a
+ Ondrag Atom = 0x3ab06
+ Ondragend Atom = 0x3ab09
+ Ondragenter Atom = 0x3b40b
+ Ondragexit Atom = 0x3bf0a
+ Ondragleave Atom = 0x3d90b
+ Ondragover Atom = 0x3e40a
+ Ondragstart Atom = 0x3ee0b
+ Ondrop Atom = 0x3fd06
+ Ondurationchange Atom = 0x40d10
+ Onemptied Atom = 0x40409
+ Onended Atom = 0x41d07
+ Onerror Atom = 0x42407
+ Onfocus Atom = 0x42b07
+ Onhashchange Atom = 0x4370c
+ Oninput Atom = 0x44307
+ Oninvalid Atom = 0x44f09
+ Onkeydown Atom = 0x45809
+ Onkeypress Atom = 0x4650a
+ Onkeyup Atom = 0x47407
+ Onlanguagechange Atom = 0x48110
+ Onload Atom = 0x49106
+ Onloadeddata Atom = 0x4910c
+ Onloadedmetadata Atom = 0x4a410
+ Onloadend Atom = 0x4ba09
+ Onloadstart Atom = 0x4c30b
+ Onmessage Atom = 0x4ce09
+ Onmessageerror Atom = 0x4ce0e
+ Onmousedown Atom = 0x4dc0b
+ Onmouseenter Atom = 0x4e70c
+ Onmouseleave Atom = 0x4f30c
+ Onmousemove Atom = 0x4ff0b
+ Onmouseout Atom = 0x50a0a
+ Onmouseover Atom = 0x5170b
+ Onmouseup Atom = 0x52209
+ Onmousewheel Atom = 0x5300c
+ Onoffline Atom = 0x53c09
+ Ononline Atom = 0x54508
+ Onpagehide Atom = 0x54d0a
+ Onpageshow Atom = 0x5630a
+ Onpaste Atom = 0x56f07
+ Onpause Atom = 0x58a07
+ Onplay Atom = 0x59406
+ Onplaying Atom = 0x59409
+ Onpopstate Atom = 0x59d0a
+ Onprogress Atom = 0x5a70a
+ Onratechange Atom = 0x5bc0c
+ Onrejectionhandled Atom = 0x5c812
+ Onreset Atom = 0x5da07
+ Onresize Atom = 0x5e108
+ Onscroll Atom = 0x5f508
+ Onsecuritypolicyviolation Atom = 0x5fd19
+ Onseeked Atom = 0x61e08
+ Onseeking Atom = 0x62609
+ Onselect Atom = 0x62f08
+ Onshow Atom = 0x63906
+ Onsort Atom = 0x64d06
+ Onstalled Atom = 0x65709
+ Onstorage Atom = 0x66009
+ Onsubmit Atom = 0x66908
+ Onsuspend Atom = 0x67909
Ontimeupdate Atom = 0x400c
- Ontoggle Atom = 0x68408
- Onunhandledrejection Atom = 0x68c14
- Onunload Atom = 0x6ab08
- Onvolumechange Atom = 0x6b30e
- Onwaiting Atom = 0x6c109
- Onwheel Atom = 0x6ca07
+ Ontoggle Atom = 0x68208
+ Onunhandledrejection Atom = 0x68a14
+ Onunload Atom = 0x6a908
+ Onvolumechange Atom = 0x6b10e
+ Onwaiting Atom = 0x6bf09
+ Onwheel Atom = 0x6c807
Open Atom = 0x1a304
Optgroup Atom = 0x5f08
- Optimum Atom = 0x6d107
- Option Atom = 0x6e306
- Output Atom = 0x51d06
+ Optimum Atom = 0x6cf07
+ Option Atom = 0x6e106
+ Output Atom = 0x51106
P Atom = 0xc01
Param Atom = 0xc05
Pattern Atom = 0x6607
@@ -288,466 +288,468 @@ const (
Placeholder Atom = 0x1310b
Plaintext Atom = 0x1b209
Playsinline Atom = 0x1400b
- Poster Atom = 0x2cf06
- Pre Atom = 0x47003
- Preload Atom = 0x48607
- Progress Atom = 0x5b908
- Prompt Atom = 0x53606
- Public Atom = 0x58606
+ Poster Atom = 0x64706
+ Pre Atom = 0x46a03
+ Preload Atom = 0x47a07
+ Progress Atom = 0x5a908
+ Prompt Atom = 0x52a06
+ Public Atom = 0x57606
Q Atom = 0xcf01
Radiogroup Atom = 0x30a
Rb Atom = 0x3a02
- Readonly Atom = 0x35708
- Referrerpolicy Atom = 0x3d10e
- Rel Atom = 0x48703
- Required Atom = 0x24c08
+ Readonly Atom = 0x35108
+ Referrerpolicy Atom = 0x3cb0e
+ Rel Atom = 0x47b03
+ Required Atom = 0x23f08
Reversed Atom = 0x8008
Rows Atom = 0x9c04
Rowspan Atom = 0x9c07
- Rp Atom = 0x23c02
+ Rp Atom = 0x22f02
Rt Atom = 0x19a02
Rtc Atom = 0x19a03
Ruby Atom = 0xfb04
S Atom = 0x2501
Samp Atom = 0x7804
Sandbox Atom = 0x12907
- Scope Atom = 0x67505
- Scoped Atom = 0x67506
- Script Atom = 0x21806
- Seamless Atom = 0x37108
- Section Atom = 0x56807
- Select Atom = 0x63c06
- Selected Atom = 0x63c08
- Shape Atom = 0x1e505
- Size Atom = 0x5f504
- Sizes Atom = 0x5f505
- Slot Atom = 0x1ef04
- Small Atom = 0x20605
- Sortable Atom = 0x65108
- Sorted Atom = 0x33706
- Source Atom = 0x37806
- Spacer Atom = 0x43706
+ Scope Atom = 0x67305
+ Scoped Atom = 0x67306
+ Script Atom = 0x2c406
+ Seamless Atom = 0x36b08
+ Search Atom = 0x55c06
+ Section Atom = 0x1e507
+ Select Atom = 0x63106
+ Selected Atom = 0x63108
+ Shape Atom = 0x1f505
+ Size Atom = 0x5e504
+ Sizes Atom = 0x5e505
+ Slot Atom = 0x20504
+ Small Atom = 0x32605
+ Sortable Atom = 0x64f08
+ Sorted Atom = 0x37206
+ Source Atom = 0x43106
+ Spacer Atom = 0x46e06
Span Atom = 0x9f04
- Spellcheck Atom = 0x4740a
- Src Atom = 0x5c003
- Srcdoc Atom = 0x5c006
- Srclang Atom = 0x5f907
- Srcset Atom = 0x6f906
- Start Atom = 0x3fa05
- Step Atom = 0x58304
+ Spellcheck Atom = 0x5b00a
+ Src Atom = 0x5e903
+ Srcdoc Atom = 0x5e906
+ Srclang Atom = 0x6f707
+ Srcset Atom = 0x6fe06
+ Start Atom = 0x3f405
+ Step Atom = 0x57304
Strike Atom = 0xd206
- Strong Atom = 0x6dd06
- Style Atom = 0x6ff05
- Sub Atom = 0x66d03
- Summary Atom = 0x70407
- Sup Atom = 0x70b03
- Svg Atom = 0x70e03
- System Atom = 0x71106
- Tabindex Atom = 0x4be08
- Table Atom = 0x59505
- Target Atom = 0x2c406
+ Strong Atom = 0x6db06
+ Style Atom = 0x70405
+ Sub Atom = 0x66b03
+ Summary Atom = 0x70907
+ Sup Atom = 0x71003
+ Svg Atom = 0x71303
+ System Atom = 0x71606
+ Tabindex Atom = 0x4b208
+ Table Atom = 0x58505
+ Target Atom = 0x2b706
Tbody Atom = 0x2705
Td Atom = 0x9202
- Template Atom = 0x71408
- Textarea Atom = 0x35208
+ Template Atom = 0x71908
+ Textarea Atom = 0x34c08
Tfoot Atom = 0xf505
Th Atom = 0x15602
- Thead Atom = 0x33005
+ Thead Atom = 0x31f05
Time Atom = 0x4204
Title Atom = 0x11005
Tr Atom = 0xcc02
Track Atom = 0x1ba05
- Translate Atom = 0x1f209
+ Translate Atom = 0x20809
Tt Atom = 0x6802
Type Atom = 0xd904
- Typemustmatch Atom = 0x2900d
+ Typemustmatch Atom = 0x2830d
U Atom = 0xb01
Ul Atom = 0xa702
Updateviacache Atom = 0x460e
- Usemap Atom = 0x59e06
+ Usemap Atom = 0x58e06
Value Atom = 0x1505
Var Atom = 0x16d03
- Video Atom = 0x2f105
- Wbr Atom = 0x57c03
- Width Atom = 0x64905
- Workertype Atom = 0x71c0a
- Wrap Atom = 0x72604
+ Video Atom = 0x2e005
+ Wbr Atom = 0x56c03
+ Width Atom = 0x63e05
+ Workertype Atom = 0x7210a
+ Wrap Atom = 0x72b04
Xmp Atom = 0x12f03
)
-const hash0 = 0x81cdf10e
+const hash0 = 0x84f70e16
const maxAtomLen = 25
var table = [1 << 9]Atom{
- 0x1: 0xe60a, // mediagroup
- 0x2: 0x2e404, // lang
- 0x4: 0x2c09, // accesskey
- 0x5: 0x8b08, // frameset
- 0x7: 0x63a08, // onselect
- 0x8: 0x71106, // system
- 0xa: 0x64905, // width
- 0xc: 0x2890b, // formenctype
- 0xd: 0x13702, // ol
- 0xe: 0x3970b, // oncuechange
- 0x10: 0x14b03, // bdo
- 0x11: 0x11505, // audio
- 0x12: 0x17a09, // draggable
- 0x14: 0x2f105, // video
- 0x15: 0x2b102, // mn
- 0x16: 0x38704, // menu
- 0x17: 0x2cf06, // poster
- 0x19: 0xf606, // footer
- 0x1a: 0x2a806, // method
- 0x1b: 0x2b808, // datetime
- 0x1c: 0x19507, // onabort
- 0x1d: 0x460e, // updateviacache
- 0x1e: 0xff05, // async
- 0x1f: 0x49d06, // onload
- 0x21: 0x11908, // oncancel
- 0x22: 0x62908, // onseeked
- 0x23: 0x30205, // image
- 0x24: 0x5d812, // onrejectionhandled
- 0x26: 0x17404, // link
- 0x27: 0x51d06, // output
- 0x28: 0x33104, // head
- 0x29: 0x4ff0c, // onmouseleave
- 0x2a: 0x57f07, // onpaste
- 0x2b: 0x5a409, // onplaying
- 0x2c: 0x1c407, // colspan
- 0x2f: 0x1bf05, // color
- 0x30: 0x5f504, // size
- 0x31: 0x2e80a, // http-equiv
- 0x33: 0x601, // i
- 0x34: 0x5590a, // onpagehide
- 0x35: 0x68c14, // onunhandledrejection
- 0x37: 0x42a07, // onerror
- 0x3a: 0x3b08, // basefont
- 0x3f: 0x1303, // nav
- 0x40: 0x17704, // kind
- 0x41: 0x35708, // readonly
- 0x42: 0x30806, // mglyph
- 0x44: 0xb202, // li
- 0x46: 0x2d506, // hidden
- 0x47: 0x70e03, // svg
- 0x48: 0x58304, // step
- 0x49: 0x23f09, // integrity
- 0x4a: 0x58606, // public
- 0x4c: 0x1ab03, // col
- 0x4d: 0x1870a, // blockquote
- 0x4e: 0x34f02, // h5
- 0x50: 0x5b908, // progress
- 0x51: 0x5f505, // sizes
- 0x52: 0x34502, // h4
- 0x56: 0x33005, // thead
- 0x57: 0xd607, // keytype
- 0x58: 0x5b70a, // onprogress
- 0x59: 0x44b09, // inputmode
- 0x5a: 0x3b109, // ondragend
- 0x5d: 0x3a205, // oncut
- 0x5e: 0x43706, // spacer
- 0x5f: 0x1ab08, // colgroup
- 0x62: 0x16502, // is
- 0x65: 0x3c02, // as
- 0x66: 0x54809, // onoffline
- 0x67: 0x33706, // sorted
- 0x69: 0x48d10, // onlanguagechange
- 0x6c: 0x43d0c, // onhashchange
- 0x6d: 0x9604, // name
- 0x6e: 0xf505, // tfoot
- 0x6f: 0x56104, // desc
- 0x70: 0x33d03, // max
- 0x72: 0x1ea06, // coords
- 0x73: 0x30d02, // h3
- 0x74: 0x6e70e, // onbeforeunload
- 0x75: 0x9c04, // rows
- 0x76: 0x63c06, // select
- 0x77: 0x9805, // meter
- 0x78: 0x38b06, // itemid
- 0x79: 0x53c0c, // onmousewheel
- 0x7a: 0x5c006, // srcdoc
- 0x7d: 0x1ba05, // track
- 0x7f: 0x31f08, // itemtype
- 0x82: 0xa402, // mo
- 0x83: 0x41b08, // onchange
- 0x84: 0x33107, // headers
- 0x85: 0x5cc0c, // onratechange
- 0x86: 0x60819, // onsecuritypolicyviolation
- 0x88: 0x4a508, // datalist
- 0x89: 0x4e80b, // onmousedown
- 0x8a: 0x1ef04, // slot
- 0x8b: 0x4b010, // onloadedmetadata
- 0x8c: 0x1a06, // accept
- 0x8d: 0x26806, // object
- 0x91: 0x6b30e, // onvolumechange
- 0x92: 0x2107, // charset
- 0x93: 0x27613, // onautocompleteerror
- 0x94: 0xc113, // allowpaymentrequest
- 0x95: 0x2804, // body
- 0x96: 0x10a07, // default
- 0x97: 0x63c08, // selected
- 0x98: 0x21e04, // face
- 0x99: 0x1e505, // shape
- 0x9b: 0x68408, // ontoggle
- 0x9e: 0x64b02, // dt
- 0x9f: 0xb604, // mark
- 0xa1: 0xb01, // u
- 0xa4: 0x6ab08, // onunload
- 0xa5: 0x5d04, // loop
- 0xa6: 0x16408, // disabled
- 0xaa: 0x42307, // onended
- 0xab: 0xb00a, // malignmark
- 0xad: 0x67b09, // onsuspend
- 0xae: 0x35105, // mtext
- 0xaf: 0x64f06, // onsort
- 0xb0: 0x19d08, // itemprop
- 0xb3: 0x67109, // itemscope
- 0xb4: 0x17305, // blink
- 0xb6: 0x3b106, // ondrag
- 0xb7: 0xa702, // ul
- 0xb8: 0x26e04, // form
- 0xb9: 0x12907, // sandbox
- 0xba: 0x8b05, // frame
- 0xbb: 0x1505, // value
- 0xbc: 0x66209, // onstorage
- 0xbf: 0xaa07, // acronym
- 0xc0: 0x19a02, // rt
- 0xc2: 0x202, // br
- 0xc3: 0x22608, // fieldset
- 0xc4: 0x2900d, // typemustmatch
- 0xc5: 0xa208, // nomodule
- 0xc6: 0x6c07, // noembed
- 0xc7: 0x69e0d, // onbeforeprint
- 0xc8: 0x19106, // button
- 0xc9: 0x2f507, // onclick
- 0xca: 0x70407, // summary
- 0xcd: 0xfb04, // ruby
- 0xce: 0x56405, // class
- 0xcf: 0x3f40b, // ondragstart
- 0xd0: 0x23107, // caption
- 0xd4: 0xdd0e, // allowusermedia
- 0xd5: 0x4cf0b, // onloadstart
- 0xd9: 0x16b03, // div
- 0xda: 0x4a904, // list
- 0xdb: 0x32e04, // math
- 0xdc: 0x44b05, // input
- 0xdf: 0x3ea0a, // ondragover
- 0xe0: 0x2de02, // h2
- 0xe2: 0x1b209, // plaintext
- 0xe4: 0x4f30c, // onmouseenter
- 0xe7: 0x47907, // checked
- 0xe8: 0x47003, // pre
- 0xea: 0x35f08, // multiple
- 0xeb: 0xba03, // bdi
- 0xec: 0x33d09, // maxlength
- 0xed: 0xcf01, // q
- 0xee: 0x61f0a, // onauxclick
- 0xf0: 0x57c03, // wbr
- 0xf2: 0x3b04, // base
- 0xf3: 0x6e306, // option
- 0xf5: 0x41310, // ondurationchange
- 0xf7: 0x8908, // noframes
- 0xf9: 0x40508, // dropzone
- 0xfb: 0x67505, // scope
- 0xfc: 0x8008, // reversed
- 0xfd: 0x3ba0b, // ondragenter
- 0xfe: 0x3fa05, // start
- 0xff: 0x12f03, // xmp
- 0x100: 0x5f907, // srclang
- 0x101: 0x30703, // img
- 0x104: 0x101, // b
- 0x105: 0x25403, // for
- 0x106: 0x10705, // aside
- 0x107: 0x44907, // oninput
- 0x108: 0x35604, // area
- 0x109: 0x2a40a, // formmethod
- 0x10a: 0x72604, // wrap
- 0x10c: 0x23c02, // rp
- 0x10d: 0x46b0a, // onkeypress
- 0x10e: 0x6802, // tt
- 0x110: 0x34702, // mi
- 0x111: 0x36705, // muted
- 0x112: 0xf303, // alt
- 0x113: 0x5c504, // code
- 0x114: 0x6e02, // em
- 0x115: 0x3c50a, // ondragexit
- 0x117: 0x9f04, // span
- 0x119: 0x6d708, // manifest
- 0x11a: 0x38708, // menuitem
- 0x11b: 0x58b07, // content
- 0x11d: 0x6c109, // onwaiting
- 0x11f: 0x4c609, // onloadend
- 0x121: 0x37e0d, // oncontextmenu
- 0x123: 0x56d06, // onblur
- 0x124: 0x3fc07, // article
- 0x125: 0x9303, // dir
- 0x126: 0xef04, // ping
- 0x127: 0x24c08, // required
- 0x128: 0x45509, // oninvalid
- 0x129: 0xb105, // align
- 0x12b: 0x58a04, // icon
- 0x12c: 0x64d02, // h6
- 0x12d: 0x1c404, // cols
- 0x12e: 0x22e0a, // figcaption
- 0x12f: 0x45e09, // onkeydown
- 0x130: 0x66b08, // onsubmit
- 0x131: 0x14d09, // oncanplay
- 0x132: 0x70b03, // sup
- 0x133: 0xc01, // p
- 0x135: 0x40a09, // onemptied
- 0x136: 0x39106, // oncopy
- 0x137: 0x19c04, // cite
- 0x138: 0x3a70a, // ondblclick
- 0x13a: 0x50b0b, // onmousemove
- 0x13c: 0x66d03, // sub
- 0x13d: 0x48703, // rel
- 0x13e: 0x5f08, // optgroup
- 0x142: 0x9c07, // rowspan
- 0x143: 0x37806, // source
- 0x144: 0x21608, // noscript
- 0x145: 0x1a304, // open
- 0x146: 0x20403, // ins
- 0x147: 0x2540d, // foreignObject
- 0x148: 0x5ad0a, // onpopstate
- 0x14a: 0x28d07, // enctype
- 0x14b: 0x2760e, // onautocomplete
- 0x14c: 0x35208, // textarea
- 0x14e: 0x2780c, // autocomplete
- 0x14f: 0x15702, // hr
- 0x150: 0x1de08, // controls
- 0x151: 0x10902, // id
- 0x153: 0x2360c, // onafterprint
- 0x155: 0x2610d, // foreignobject
- 0x156: 0x32707, // marquee
- 0x157: 0x59a07, // onpause
- 0x158: 0x5e602, // dl
- 0x159: 0x5206, // height
- 0x15a: 0x34703, // min
- 0x15b: 0x9307, // dirname
- 0x15c: 0x1f209, // translate
- 0x15d: 0x5604, // html
- 0x15e: 0x34709, // minlength
- 0x15f: 0x48607, // preload
- 0x160: 0x71408, // template
- 0x161: 0x3df0b, // ondragleave
- 0x162: 0x3a02, // rb
- 0x164: 0x5c003, // src
- 0x165: 0x6dd06, // strong
- 0x167: 0x7804, // samp
- 0x168: 0x6f307, // address
- 0x169: 0x55108, // ononline
- 0x16b: 0x1310b, // placeholder
- 0x16c: 0x2c406, // target
- 0x16d: 0x20605, // small
- 0x16e: 0x6ca07, // onwheel
- 0x16f: 0x1c90a, // annotation
- 0x170: 0x4740a, // spellcheck
- 0x171: 0x7207, // details
- 0x172: 0x10306, // canvas
- 0x173: 0x12109, // autofocus
- 0x174: 0xc05, // param
- 0x176: 0x46308, // download
- 0x177: 0x45203, // del
- 0x178: 0x36c07, // onclose
- 0x179: 0xb903, // kbd
- 0x17a: 0x31906, // applet
- 0x17b: 0x2e004, // href
- 0x17c: 0x5f108, // onresize
- 0x17e: 0x49d0c, // onloadeddata
- 0x180: 0xcc02, // tr
- 0x181: 0x2c00a, // formtarget
- 0x182: 0x11005, // title
- 0x183: 0x6ff05, // style
- 0x184: 0xd206, // strike
- 0x185: 0x59e06, // usemap
- 0x186: 0x2fc06, // iframe
- 0x187: 0x1004, // main
- 0x189: 0x7b07, // picture
- 0x18c: 0x31605, // ismap
- 0x18e: 0x4a504, // data
- 0x18f: 0x5905, // label
- 0x191: 0x3d10e, // referrerpolicy
- 0x192: 0x15602, // th
- 0x194: 0x53606, // prompt
- 0x195: 0x56807, // section
- 0x197: 0x6d107, // optimum
- 0x198: 0x2db04, // high
- 0x199: 0x15c02, // h1
- 0x19a: 0x65909, // onstalled
- 0x19b: 0x16d03, // var
- 0x19c: 0x4204, // time
- 0x19e: 0x67402, // ms
- 0x19f: 0x33106, // header
- 0x1a0: 0x4da09, // onmessage
- 0x1a1: 0x1a605, // nonce
- 0x1a2: 0x26e0a, // formaction
- 0x1a3: 0x22006, // center
- 0x1a4: 0x3704, // nobr
- 0x1a5: 0x59505, // table
- 0x1a6: 0x4a907, // listing
- 0x1a7: 0x18106, // legend
- 0x1a9: 0x29b09, // challenge
- 0x1aa: 0x24806, // figure
- 0x1ab: 0xe605, // media
- 0x1ae: 0xd904, // type
- 0x1af: 0x3f04, // font
- 0x1b0: 0x4da0e, // onmessageerror
- 0x1b1: 0x37108, // seamless
- 0x1b2: 0x8703, // dfn
- 0x1b3: 0x5c705, // defer
- 0x1b4: 0xc303, // low
- 0x1b5: 0x19a03, // rtc
- 0x1b6: 0x5230b, // onmouseover
- 0x1b7: 0x2b20a, // novalidate
- 0x1b8: 0x71c0a, // workertype
- 0x1ba: 0x3cd07, // itemref
- 0x1bd: 0x1, // a
- 0x1be: 0x31803, // map
- 0x1bf: 0x400c, // ontimeupdate
- 0x1c0: 0x15e07, // bgsound
- 0x1c1: 0x3206, // keygen
- 0x1c2: 0x2705, // tbody
- 0x1c5: 0x64406, // onshow
- 0x1c7: 0x2501, // s
- 0x1c8: 0x6607, // pattern
- 0x1cc: 0x14d10, // oncanplaythrough
- 0x1ce: 0x2d702, // dd
- 0x1cf: 0x6f906, // srcset
- 0x1d0: 0x17003, // big
- 0x1d2: 0x65108, // sortable
- 0x1d3: 0x48007, // onkeyup
- 0x1d5: 0x5a406, // onplay
- 0x1d7: 0x4b804, // meta
- 0x1d8: 0x40306, // ondrop
- 0x1da: 0x60008, // onscroll
- 0x1db: 0x1fb0b, // crossorigin
- 0x1dc: 0x5730a, // onpageshow
- 0x1dd: 0x4, // abbr
- 0x1de: 0x9202, // td
- 0x1df: 0x58b0f, // contenteditable
- 0x1e0: 0x27206, // action
- 0x1e1: 0x1400b, // playsinline
- 0x1e2: 0x43107, // onfocus
- 0x1e3: 0x2e008, // hreflang
- 0x1e5: 0x5160a, // onmouseout
- 0x1e6: 0x5ea07, // onreset
- 0x1e7: 0x13c08, // autoplay
- 0x1e8: 0x63109, // onseeking
- 0x1ea: 0x67506, // scoped
- 0x1ec: 0x30a, // radiogroup
- 0x1ee: 0x3800b, // contextmenu
- 0x1ef: 0x52e09, // onmouseup
- 0x1f1: 0x2ca06, // hgroup
- 0x1f2: 0x2080f, // allowfullscreen
- 0x1f3: 0x4be08, // tabindex
- 0x1f6: 0x30f07, // isindex
- 0x1f7: 0x1a0e, // accept-charset
- 0x1f8: 0x2ae0e, // formnovalidate
- 0x1fb: 0x1c90e, // annotation-xml
- 0x1fc: 0x6e05, // embed
- 0x1fd: 0x21806, // script
- 0x1fe: 0xbb06, // dialog
- 0x1ff: 0x1d707, // command
+ 0x1: 0x3ff08, // dropzone
+ 0x2: 0x3b08, // basefont
+ 0x3: 0x23209, // integrity
+ 0x4: 0x43106, // source
+ 0x5: 0x2c09, // accesskey
+ 0x6: 0x1a06, // accept
+ 0x7: 0x6c807, // onwheel
+ 0xb: 0x47407, // onkeyup
+ 0xc: 0x32007, // headers
+ 0xd: 0x67306, // scoped
+ 0xe: 0x67909, // onsuspend
+ 0xf: 0x8908, // noframes
+ 0x10: 0x1fa0b, // crossorigin
+ 0x11: 0x2e407, // onclick
+ 0x12: 0x3f405, // start
+ 0x13: 0x37a0b, // contextmenu
+ 0x14: 0x5e903, // src
+ 0x15: 0x1c404, // cols
+ 0x16: 0xbb06, // dialog
+ 0x17: 0x47a07, // preload
+ 0x18: 0x3c707, // itemref
+ 0x1b: 0x2f105, // image
+ 0x1d: 0x4ba09, // onloadend
+ 0x1e: 0x45d08, // download
+ 0x1f: 0x46a03, // pre
+ 0x23: 0x2970a, // formmethod
+ 0x24: 0x71303, // svg
+ 0x25: 0xcf01, // q
+ 0x26: 0x64002, // dt
+ 0x27: 0x1de08, // controls
+ 0x2a: 0x2804, // body
+ 0x2b: 0xd206, // strike
+ 0x2c: 0x3910b, // oncuechange
+ 0x2d: 0x4c30b, // onloadstart
+ 0x2e: 0x2fe07, // isindex
+ 0x2f: 0xb202, // li
+ 0x30: 0x1400b, // playsinline
+ 0x31: 0x34102, // mi
+ 0x32: 0x30806, // applet
+ 0x33: 0x4ce09, // onmessage
+ 0x35: 0x13702, // ol
+ 0x36: 0x1a304, // open
+ 0x39: 0x14d09, // oncanplay
+ 0x3a: 0x6bf09, // onwaiting
+ 0x3b: 0x11908, // oncancel
+ 0x3c: 0x6a908, // onunload
+ 0x3e: 0x53c09, // onoffline
+ 0x3f: 0x1a0e, // accept-charset
+ 0x40: 0x32004, // head
+ 0x42: 0x3ab09, // ondragend
+ 0x43: 0x1310b, // placeholder
+ 0x44: 0x2b30a, // formtarget
+ 0x45: 0x2540d, // foreignobject
+ 0x47: 0x400c, // ontimeupdate
+ 0x48: 0xdd0e, // allowusermedia
+ 0x4a: 0x69c0d, // onbeforeprint
+ 0x4b: 0x5604, // html
+ 0x4c: 0x9f04, // span
+ 0x4d: 0x64206, // hgroup
+ 0x4e: 0x16408, // disabled
+ 0x4f: 0x4204, // time
+ 0x51: 0x42b07, // onfocus
+ 0x53: 0xb00a, // malignmark
+ 0x55: 0x4650a, // onkeypress
+ 0x56: 0x55805, // class
+ 0x57: 0x1ab08, // colgroup
+ 0x58: 0x33709, // maxlength
+ 0x59: 0x5a908, // progress
+ 0x5b: 0x70405, // style
+ 0x5c: 0x2a10e, // formnovalidate
+ 0x5e: 0x38b06, // oncopy
+ 0x60: 0x26104, // form
+ 0x61: 0xf606, // footer
+ 0x64: 0x30a, // radiogroup
+ 0x66: 0xfb04, // ruby
+ 0x67: 0x4ff0b, // onmousemove
+ 0x68: 0x19d08, // itemprop
+ 0x69: 0x2d70a, // http-equiv
+ 0x6a: 0x15602, // th
+ 0x6c: 0x6e02, // em
+ 0x6d: 0x38108, // menuitem
+ 0x6e: 0x63106, // select
+ 0x6f: 0x48110, // onlanguagechange
+ 0x70: 0x31f05, // thead
+ 0x71: 0x15c02, // h1
+ 0x72: 0x5e906, // srcdoc
+ 0x75: 0x9604, // name
+ 0x76: 0x19106, // button
+ 0x77: 0x55504, // desc
+ 0x78: 0x17704, // kind
+ 0x79: 0x1bf05, // color
+ 0x7c: 0x58e06, // usemap
+ 0x7d: 0x30e08, // itemtype
+ 0x7f: 0x6d508, // manifest
+ 0x81: 0x5300c, // onmousewheel
+ 0x82: 0x4dc0b, // onmousedown
+ 0x84: 0xc05, // param
+ 0x85: 0x2e005, // video
+ 0x86: 0x4910c, // onloadeddata
+ 0x87: 0x6f107, // address
+ 0x8c: 0xef04, // ping
+ 0x8d: 0x24703, // for
+ 0x8f: 0x62f08, // onselect
+ 0x90: 0x30703, // map
+ 0x92: 0xc01, // p
+ 0x93: 0x8008, // reversed
+ 0x94: 0x54d0a, // onpagehide
+ 0x95: 0x3206, // keygen
+ 0x96: 0x34109, // minlength
+ 0x97: 0x3e40a, // ondragover
+ 0x98: 0x42407, // onerror
+ 0x9a: 0x2107, // charset
+ 0x9b: 0x29b06, // method
+ 0x9c: 0x101, // b
+ 0x9d: 0x68208, // ontoggle
+ 0x9e: 0x2bd06, // hidden
+ 0xa0: 0x3f607, // article
+ 0xa2: 0x63906, // onshow
+ 0xa3: 0x64d06, // onsort
+ 0xa5: 0x57b0f, // contenteditable
+ 0xa6: 0x66908, // onsubmit
+ 0xa8: 0x44f09, // oninvalid
+ 0xaa: 0x202, // br
+ 0xab: 0x10902, // id
+ 0xac: 0x5d04, // loop
+ 0xad: 0x5630a, // onpageshow
+ 0xb0: 0x2cf04, // href
+ 0xb2: 0x2210a, // figcaption
+ 0xb3: 0x2690e, // onautocomplete
+ 0xb4: 0x49106, // onload
+ 0xb6: 0x9c04, // rows
+ 0xb7: 0x1a605, // nonce
+ 0xb8: 0x68a14, // onunhandledrejection
+ 0xbb: 0x21306, // center
+ 0xbc: 0x59406, // onplay
+ 0xbd: 0x33f02, // h5
+ 0xbe: 0x49d07, // listing
+ 0xbf: 0x57606, // public
+ 0xc2: 0x23b06, // figure
+ 0xc3: 0x57a04, // icon
+ 0xc4: 0x1ab03, // col
+ 0xc5: 0x47b03, // rel
+ 0xc6: 0xe605, // media
+ 0xc7: 0x12109, // autofocus
+ 0xc8: 0x19a02, // rt
+ 0xca: 0x2d304, // lang
+ 0xcc: 0x49908, // datalist
+ 0xce: 0x2eb06, // iframe
+ 0xcf: 0x36105, // muted
+ 0xd0: 0x6140a, // onauxclick
+ 0xd2: 0x3c02, // as
+ 0xd6: 0x3fd06, // ondrop
+ 0xd7: 0x1c90a, // annotation
+ 0xd8: 0x21908, // fieldset
+ 0xdb: 0x2cf08, // hreflang
+ 0xdc: 0x4e70c, // onmouseenter
+ 0xdd: 0x2a402, // mn
+ 0xde: 0xe60a, // mediagroup
+ 0xdf: 0x9805, // meter
+ 0xe0: 0x56c03, // wbr
+ 0xe2: 0x63e05, // width
+ 0xe3: 0x2290c, // onafterprint
+ 0xe4: 0x30505, // ismap
+ 0xe5: 0x1505, // value
+ 0xe7: 0x1303, // nav
+ 0xe8: 0x54508, // ononline
+ 0xe9: 0xb604, // mark
+ 0xea: 0xc303, // low
+ 0xeb: 0x3ee0b, // ondragstart
+ 0xef: 0x12f03, // xmp
+ 0xf0: 0x22407, // caption
+ 0xf1: 0xd904, // type
+ 0xf2: 0x70907, // summary
+ 0xf3: 0x6802, // tt
+ 0xf4: 0x20809, // translate
+ 0xf5: 0x1870a, // blockquote
+ 0xf8: 0x15702, // hr
+ 0xfa: 0x2705, // tbody
+ 0xfc: 0x7b07, // picture
+ 0xfd: 0x5206, // height
+ 0xfe: 0x19c04, // cite
+ 0xff: 0x2501, // s
+ 0x101: 0xff05, // async
+ 0x102: 0x56f07, // onpaste
+ 0x103: 0x19507, // onabort
+ 0x104: 0x2b706, // target
+ 0x105: 0x14b03, // bdo
+ 0x106: 0x1f006, // coords
+ 0x107: 0x5e108, // onresize
+ 0x108: 0x71908, // template
+ 0x10a: 0x3a02, // rb
+ 0x10b: 0x2a50a, // novalidate
+ 0x10c: 0x460e, // updateviacache
+ 0x10d: 0x71003, // sup
+ 0x10e: 0x6c07, // noembed
+ 0x10f: 0x16b03, // div
+ 0x110: 0x6f707, // srclang
+ 0x111: 0x17a09, // draggable
+ 0x112: 0x67305, // scope
+ 0x113: 0x5905, // label
+ 0x114: 0x22f02, // rp
+ 0x115: 0x23f08, // required
+ 0x116: 0x3780d, // oncontextmenu
+ 0x117: 0x5e504, // size
+ 0x118: 0x5b00a, // spellcheck
+ 0x119: 0x3f04, // font
+ 0x11a: 0x9c07, // rowspan
+ 0x11b: 0x10a07, // default
+ 0x11d: 0x44307, // oninput
+ 0x11e: 0x38506, // itemid
+ 0x11f: 0x5ee04, // code
+ 0x120: 0xaa07, // acronym
+ 0x121: 0x3b04, // base
+ 0x125: 0x2470d, // foreignObject
+ 0x126: 0x2ca04, // high
+ 0x127: 0x3cb0e, // referrerpolicy
+ 0x128: 0x33703, // max
+ 0x129: 0x59d0a, // onpopstate
+ 0x12a: 0x2fc02, // h4
+ 0x12b: 0x4ac04, // meta
+ 0x12c: 0x17305, // blink
+ 0x12e: 0x5f508, // onscroll
+ 0x12f: 0x59409, // onplaying
+ 0x130: 0xc113, // allowpaymentrequest
+ 0x131: 0x19a03, // rtc
+ 0x132: 0x72b04, // wrap
+ 0x134: 0x8b08, // frameset
+ 0x135: 0x32605, // small
+ 0x137: 0x32006, // header
+ 0x138: 0x40409, // onemptied
+ 0x139: 0x34902, // h6
+ 0x13a: 0x35908, // multiple
+ 0x13c: 0x52a06, // prompt
+ 0x13f: 0x28e09, // challenge
+ 0x141: 0x4370c, // onhashchange
+ 0x142: 0x57b07, // content
+ 0x143: 0x1c90e, // annotation-xml
+ 0x144: 0x36607, // onclose
+ 0x145: 0x14d10, // oncanplaythrough
+ 0x148: 0x5170b, // onmouseover
+ 0x149: 0x64f08, // sortable
+ 0x14a: 0xa402, // mo
+ 0x14b: 0x2cd02, // h3
+ 0x14c: 0x2c406, // script
+ 0x14d: 0x41d07, // onended
+ 0x14f: 0x64706, // poster
+ 0x150: 0x7210a, // workertype
+ 0x153: 0x1f505, // shape
+ 0x154: 0x4, // abbr
+ 0x155: 0x1, // a
+ 0x156: 0x2bf02, // dd
+ 0x157: 0x71606, // system
+ 0x158: 0x4ce0e, // onmessageerror
+ 0x159: 0x36b08, // seamless
+ 0x15a: 0x2610a, // formaction
+ 0x15b: 0x6e106, // option
+ 0x15c: 0x31d04, // math
+ 0x15d: 0x62609, // onseeking
+ 0x15e: 0x39c05, // oncut
+ 0x15f: 0x44c03, // del
+ 0x160: 0x11005, // title
+ 0x161: 0x11505, // audio
+ 0x162: 0x63108, // selected
+ 0x165: 0x3b40b, // ondragenter
+ 0x166: 0x46e06, // spacer
+ 0x167: 0x4a410, // onloadedmetadata
+ 0x168: 0x44505, // input
+ 0x16a: 0x58505, // table
+ 0x16b: 0x41508, // onchange
+ 0x16e: 0x5f005, // defer
+ 0x171: 0x50a0a, // onmouseout
+ 0x172: 0x20504, // slot
+ 0x175: 0x3704, // nobr
+ 0x177: 0x1d707, // command
+ 0x17a: 0x7207, // details
+ 0x17b: 0x38104, // menu
+ 0x17c: 0xb903, // kbd
+ 0x17d: 0x57304, // step
+ 0x17e: 0x20303, // ins
+ 0x17f: 0x13c08, // autoplay
+ 0x182: 0x34103, // min
+ 0x183: 0x17404, // link
+ 0x185: 0x40d10, // ondurationchange
+ 0x186: 0x9202, // td
+ 0x187: 0x8b05, // frame
+ 0x18a: 0x2ab08, // datetime
+ 0x18b: 0x44509, // inputmode
+ 0x18c: 0x35108, // readonly
+ 0x18d: 0x21104, // face
+ 0x18f: 0x5e505, // sizes
+ 0x191: 0x4b208, // tabindex
+ 0x192: 0x6db06, // strong
+ 0x193: 0xba03, // bdi
+ 0x194: 0x6fe06, // srcset
+ 0x196: 0x67202, // ms
+ 0x197: 0x5b507, // checked
+ 0x198: 0xb105, // align
+ 0x199: 0x1e507, // section
+ 0x19b: 0x6e05, // embed
+ 0x19d: 0x15e07, // bgsound
+ 0x1a2: 0x49d04, // list
+ 0x1a3: 0x61e08, // onseeked
+ 0x1a4: 0x66009, // onstorage
+ 0x1a5: 0x2f603, // img
+ 0x1a6: 0xf505, // tfoot
+ 0x1a9: 0x26913, // onautocompleteerror
+ 0x1aa: 0x5fd19, // onsecuritypolicyviolation
+ 0x1ad: 0x9303, // dir
+ 0x1ae: 0x9307, // dirname
+ 0x1b0: 0x5a70a, // onprogress
+ 0x1b2: 0x65709, // onstalled
+ 0x1b5: 0x66f09, // itemscope
+ 0x1b6: 0x49904, // data
+ 0x1b7: 0x3d90b, // ondragleave
+ 0x1b8: 0x56102, // h2
+ 0x1b9: 0x2f706, // mglyph
+ 0x1ba: 0x16502, // is
+ 0x1bb: 0x6e50e, // onbeforeunload
+ 0x1bc: 0x2830d, // typemustmatch
+ 0x1bd: 0x3ab06, // ondrag
+ 0x1be: 0x5da07, // onreset
+ 0x1c0: 0x51106, // output
+ 0x1c1: 0x12907, // sandbox
+ 0x1c2: 0x1b209, // plaintext
+ 0x1c4: 0x34c08, // textarea
+ 0x1c7: 0xd607, // keytype
+ 0x1c8: 0x34b05, // mtext
+ 0x1c9: 0x6b10e, // onvolumechange
+ 0x1ca: 0x1ea06, // onblur
+ 0x1cb: 0x58a07, // onpause
+ 0x1cd: 0x5bc0c, // onratechange
+ 0x1ce: 0x10705, // aside
+ 0x1cf: 0x6cf07, // optimum
+ 0x1d1: 0x45809, // onkeydown
+ 0x1d2: 0x1c407, // colspan
+ 0x1d3: 0x1004, // main
+ 0x1d4: 0x66b03, // sub
+ 0x1d5: 0x25b06, // object
+ 0x1d6: 0x55c06, // search
+ 0x1d7: 0x37206, // sorted
+ 0x1d8: 0x17003, // big
+ 0x1d9: 0xb01, // u
+ 0x1db: 0x26b0c, // autocomplete
+ 0x1dc: 0xcc02, // tr
+ 0x1dd: 0xf303, // alt
+ 0x1df: 0x7804, // samp
+ 0x1e0: 0x5c812, // onrejectionhandled
+ 0x1e1: 0x4f30c, // onmouseleave
+ 0x1e2: 0x28007, // enctype
+ 0x1e3: 0xa208, // nomodule
+ 0x1e5: 0x3280f, // allowfullscreen
+ 0x1e6: 0x5f08, // optgroup
+ 0x1e8: 0x27c0b, // formenctype
+ 0x1e9: 0x18106, // legend
+ 0x1ea: 0x10306, // canvas
+ 0x1eb: 0x6607, // pattern
+ 0x1ec: 0x2c208, // noscript
+ 0x1ed: 0x601, // i
+ 0x1ee: 0x5d602, // dl
+ 0x1ef: 0xa702, // ul
+ 0x1f2: 0x52209, // onmouseup
+ 0x1f4: 0x1ba05, // track
+ 0x1f7: 0x3a10a, // ondblclick
+ 0x1f8: 0x3bf0a, // ondragexit
+ 0x1fa: 0x8703, // dfn
+ 0x1fc: 0x26506, // action
+ 0x1fd: 0x35004, // area
+ 0x1fe: 0x31607, // marquee
+ 0x1ff: 0x16d03, // var
}
const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" +
@@ -758,26 +760,26 @@ const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb"
"dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" +
"bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" +
"penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" +
- "ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" +
- "ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" +
- "ignObjectforeignobjectformactionautocompleteerrorformenctype" +
- "mustmatchallengeformmethodformnovalidatetimeformtargethgroup" +
- "osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" +
- "ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" +
- "inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" +
- "extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" +
- "enterondragexitemreferrerpolicyondragleaveondragoverondragst" +
- "articleondropzonemptiedondurationchangeonendedonerroronfocus" +
- "paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" +
- "spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" +
- "onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" +
- "usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" +
- "seoveronmouseupromptonmousewheelonofflineononlineonpagehides" +
- "classectionbluronpageshowbronpastepublicontenteditableonpaus" +
- "emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" +
- "jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" +
- "violationauxclickonseekedonseekingonselectedonshowidth6onsor" +
- "tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" +
- "handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" +
- "wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" +
- "arysupsvgsystemplateworkertypewrap"
+ "ntrolsectionblurcoordshapecrossoriginslotranslatefacenterfie" +
+ "ldsetfigcaptionafterprintegrityfigurequiredforeignObjectfore" +
+ "ignobjectformactionautocompleteerrorformenctypemustmatchalle" +
+ "ngeformmethodformnovalidatetimeformtargethiddenoscripthigh3h" +
+ "reflanghttp-equivideonclickiframeimageimglyph4isindexismappl" +
+ "etitemtypemarqueematheadersmallowfullscreenmaxlength5minleng" +
+ "th6mtextareadonlymultiplemutedoncloseamlessortedoncontextmen" +
+ "uitemidoncopyoncuechangeoncutondblclickondragendondragentero" +
+ "ndragexitemreferrerpolicyondragleaveondragoverondragstarticl" +
+ "eondropzonemptiedondurationchangeonendedonerroronfocusourceo" +
+ "nhashchangeoninputmodeloninvalidonkeydownloadonkeypresspacer" +
+ "onkeyupreloadonlanguagechangeonloadeddatalistingonloadedmeta" +
+ "databindexonloadendonloadstartonmessageerroronmousedownonmou" +
+ "seenteronmouseleaveonmousemoveonmouseoutputonmouseoveronmous" +
+ "eupromptonmousewheelonofflineononlineonpagehidesclassearch2o" +
+ "npageshowbronpastepublicontenteditableonpausemaponplayingonp" +
+ "opstateonprogresspellcheckedonratechangeonrejectionhandledon" +
+ "resetonresizesrcdocodeferonscrollonsecuritypolicyviolationau" +
+ "xclickonseekedonseekingonselectedonshowidthgrouposteronsorta" +
+ "bleonstalledonstorageonsubmitemscopedonsuspendontoggleonunha" +
+ "ndledrejectionbeforeprintonunloadonvolumechangeonwaitingonwh" +
+ "eeloptimumanifestrongoptionbeforeunloaddressrclangsrcsetstyl" +
+ "esummarysupsvgsystemplateworkertypewrap"
diff --git a/html/atom/table_test.go b/html/atom/table_test.go
index 8a30762ec2..f5c3cdb4fc 100644
--- a/html/atom/table_test.go
+++ b/html/atom/table_test.go
@@ -315,6 +315,7 @@ var testAtomList = []string{
"scoped",
"script",
"seamless",
+ "search",
"section",
"select",
"selected",
diff --git a/html/parse.go b/html/parse.go
index 643c674e37..518ee4c94e 100644
--- a/html/parse.go
+++ b/html/parse.go
@@ -924,7 +924,7 @@ func inBodyIM(p *parser) bool {
p.addElement()
p.im = inFramesetIM
return true
- case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Main, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+ case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Main, a.Menu, a.Nav, a.Ol, a.P, a.Search, a.Section, a.Summary, a.Ul:
p.popUntil(buttonScope, a.P)
p.addElement()
case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
@@ -1136,7 +1136,7 @@ func inBodyIM(p *parser) bool {
return false
}
return true
- case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Main, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+ case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Main, a.Menu, a.Nav, a.Ol, a.Pre, a.Search, a.Section, a.Summary, a.Ul:
p.popUntil(defaultScope, p.tok.DataAtom)
case a.Form:
if p.oe.contains(a.Template) {
diff --git a/html/parse_test.go b/html/parse_test.go
index 24b352e2ad..fea110a4b3 100644
--- a/html/parse_test.go
+++ b/html/parse_test.go
@@ -476,6 +476,23 @@ func TestParseFragmentForeignContentTemplates(t *testing.T) {
}
}
+func TestSearchTagClosesP(t *testing.T) {
+ data := `
Unclosed paragraphSearch content`
+ node, err := Parse(strings.NewReader(data))
+ if err != nil {
+ t.Fatalf("Error parsing HTML: %v", err)
+ }
+
+ var builder strings.Builder
+ Render(&builder, node)
+ output := builder.String()
+
+ expected := `
Unclosed paragraph
Search content`
+ if output != expected {
+ t.Errorf("Parse(%q) = %q, want %q", data, output, expected)
+ }
+}
+
func BenchmarkParser(b *testing.B) {
buf, err := os.ReadFile("testdata/go1.html")
if err != nil {
diff --git a/html/token.go b/html/token.go
index 3c57880d69..6598c1f7b3 100644
--- a/html/token.go
+++ b/html/token.go
@@ -839,8 +839,22 @@ func (z *Tokenizer) readStartTag() TokenType {
if raw {
z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end]))
}
- // Look for a self-closing token like "
".
- if z.err == nil && z.buf[z.raw.end-2] == '/' {
+ // Look for a self-closing token (e.g.
).
+ //
+ // Originally, we did this by just checking that the last character of the
+ // tag (ignoring the closing bracket) was a solidus (/) character, but this
+ // is not always accurate.
+ //
+ // We need to be careful that we don't misinterpret a non-self-closing tag
+ // as self-closing, as can happen if the tag contains unquoted attribute
+ // values (i.e. ).
+ //
+ // To avoid this, we check that the last non-bracket character of the tag
+ // (z.raw.end-2) isn't the same character as the last non-quote character of
+ // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has
+ // attributes.
+ nAttrs := len(z.attr)
+ if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) {
return SelfClosingTagToken
}
return StartTagToken
diff --git a/html/token_test.go b/html/token_test.go
index a36d112d74..44773f1712 100644
--- a/html/token_test.go
+++ b/html/token_test.go
@@ -616,6 +616,16 @@ var tokenTests = []tokenTest{
`
`,
`
`,
},
+ {
+ "slash at end of unquoted attribute value",
+ `
`,
+ `
`,
+ },
+ {
+ "self-closing tag with attribute",
+ `
`,
+ `
`,
+ },
}
func TestTokenizer(t *testing.T) {
@@ -815,6 +825,14 @@ func TestReaderEdgeCases(t *testing.T) {
}
}
+func TestSelfClosingTagValueConfusion(t *testing.T) {
+ z := NewTokenizer(strings.NewReader(`
`))
+ tok := z.Next()
+ if tok != StartTagToken {
+ t.Fatalf("unexpected token type: got %s, want %s", tok, StartTagToken)
+ }
+}
+
// zeroOneByteReader is like a strings.Reader that alternates between
// returning 0 bytes and 1 byte at a time.
type zeroOneByteReader struct {
diff --git a/http/httpproxy/proxy.go b/http/httpproxy/proxy.go
index 6404aaf157..d89c257ae7 100644
--- a/http/httpproxy/proxy.go
+++ b/http/httpproxy/proxy.go
@@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"net"
+ "net/netip"
"net/url"
"os"
"strings"
@@ -177,8 +178,10 @@ func (cfg *config) useProxy(addr string) bool {
if host == "localhost" {
return false
}
- ip := net.ParseIP(host)
- if ip != nil {
+ nip, err := netip.ParseAddr(host)
+ var ip net.IP
+ if err == nil {
+ ip = net.IP(nip.AsSlice())
if ip.IsLoopback() {
return false
}
@@ -360,6 +363,9 @@ type domainMatch struct {
}
func (m domainMatch) match(host, port string, ip net.IP) bool {
+ if ip != nil {
+ return false
+ }
if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) {
return m.port == "" || m.port == port
}
diff --git a/http/httpproxy/proxy_test.go b/http/httpproxy/proxy_test.go
index 790afdab77..a1dd2e83fd 100644
--- a/http/httpproxy/proxy_test.go
+++ b/http/httpproxy/proxy_test.go
@@ -211,6 +211,13 @@ var proxyForURLTests = []proxyForURLTest{{
},
req: "http://www.xn--fsq092h.com",
want: "",
+}, {
+ cfg: httpproxy.Config{
+ NoProxy: "example.com",
+ HTTPProxy: "proxy",
+ },
+ req: "http://[1000::%25.example.com]:123",
+ want: "http://proxy",
},
}
diff --git a/http2/clientconn_test.go b/http2/clientconn_test.go
index 42d9fd2dcc..f9e9a2fdaa 100644
--- a/http2/clientconn_test.go
+++ b/http2/clientconn_test.go
@@ -20,6 +20,7 @@ import (
"time"
"golang.org/x/net/http2/hpack"
+ "golang.org/x/net/internal/gate"
)
// TestTestClientConn demonstrates usage of testClientConn.
@@ -206,7 +207,7 @@ func (tc *testClientConn) closeWrite() {
// testRequestBody is a Request.Body for use in tests.
type testRequestBody struct {
tc *testClientConn
- gate gate
+ gate gate.Gate
// At most one of buf or bytes can be set at any given time:
buf bytes.Buffer // specific bytes to read from the body
@@ -218,18 +219,18 @@ type testRequestBody struct {
func (tc *testClientConn) newRequestBody() *testRequestBody {
b := &testRequestBody{
tc: tc,
- gate: newGate(),
+ gate: gate.New(false),
}
return b
}
func (b *testRequestBody) unlock() {
- b.gate.unlock(b.buf.Len() > 0 || b.bytes > 0 || b.err != nil)
+ b.gate.Unlock(b.buf.Len() > 0 || b.bytes > 0 || b.err != nil)
}
// Read is called by the ClientConn to read from a request body.
func (b *testRequestBody) Read(p []byte) (n int, _ error) {
- if err := b.gate.waitAndLock(context.Background()); err != nil {
+ if err := b.gate.WaitAndLock(context.Background()); err != nil {
return 0, err
}
defer b.unlock()
@@ -258,7 +259,7 @@ func (b *testRequestBody) Close() error {
// writeBytes adds n arbitrary bytes to the body.
func (b *testRequestBody) writeBytes(n int) {
defer b.tc.sync()
- b.gate.lock()
+ b.gate.Lock()
defer b.unlock()
b.bytes += n
b.checkWrite()
@@ -268,7 +269,7 @@ func (b *testRequestBody) writeBytes(n int) {
// Write adds bytes to the body.
func (b *testRequestBody) Write(p []byte) (int, error) {
defer b.tc.sync()
- b.gate.lock()
+ b.gate.Lock()
defer b.unlock()
n, err := b.buf.Write(p)
b.checkWrite()
@@ -287,7 +288,7 @@ func (b *testRequestBody) checkWrite() {
// closeWithError sets an error which will be returned by Read.
func (b *testRequestBody) closeWithError(err error) {
defer b.tc.sync()
- b.gate.lock()
+ b.gate.Lock()
defer b.unlock()
b.err = err
}
diff --git a/http2/config.go b/http2/config.go
index de58dfb8dc..ca645d9a1a 100644
--- a/http2/config.go
+++ b/http2/config.go
@@ -60,7 +60,7 @@ func configFromServer(h1 *http.Server, h2 *Server) http2Config {
return conf
}
-// configFromServer merges configuration settings from h2 and h2.t1.HTTP2
+// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2
// (the net/http Transport).
func configFromTransport(h2 *Transport) http2Config {
conf := http2Config{
diff --git a/http2/config_go124.go b/http2/config_go124.go
index e3784123c8..5b516c55ff 100644
--- a/http2/config_go124.go
+++ b/http2/config_go124.go
@@ -13,7 +13,7 @@ func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {
fillNetHTTPConfig(conf, srv.HTTP2)
}
-// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2.
+// fillNetHTTPTransportConfig sets fields in conf from tr.HTTP2.
func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {
fillNetHTTPConfig(conf, tr.HTTP2)
}
diff --git a/http2/frame.go b/http2/frame.go
index 81faec7e75..97bd8b06f7 100644
--- a/http2/frame.go
+++ b/http2/frame.go
@@ -225,6 +225,11 @@ var fhBytes = sync.Pool{
},
}
+func invalidHTTP1LookingFrameHeader() FrameHeader {
+ fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 "))
+ return fh
+}
+
// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader.
// Most users should use Framer.ReadFrame instead.
func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
@@ -503,10 +508,16 @@ func (fr *Framer) ReadFrame() (Frame, error) {
return nil, err
}
if fh.Length > fr.maxReadSize {
+ if fh == invalidHTTP1LookingFrameHeader() {
+ return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
+ }
return nil, ErrFrameTooLarge
}
payload := fr.getReadBuf(fh.Length)
if _, err := io.ReadFull(fr.r, payload); err != nil {
+ if fh == invalidHTTP1LookingFrameHeader() {
+ return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
+ }
return nil, err
}
f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload)
diff --git a/http2/h2c/h2c.go b/http2/h2c/h2c.go
index 2d6bf861b9..19e94791df 100644
--- a/http2/h2c/h2c.go
+++ b/http2/h2c/h2c.go
@@ -132,11 +132,8 @@ func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// of the body, and reforward the client preface on the net.Conn this function
// creates.
func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
- hijacker, ok := w.(http.Hijacker)
- if !ok {
- return nil, errors.New("h2c: connection does not support Hijack")
- }
- conn, rw, err := hijacker.Hijack()
+ rc := http.NewResponseController(w)
+ conn, rw, err := rc.Hijack()
if err != nil {
return nil, err
}
@@ -163,10 +160,6 @@ func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []
if err != nil {
return nil, nil, err
}
- hijacker, ok := w.(http.Hijacker)
- if !ok {
- return nil, nil, errors.New("h2c: connection does not support Hijack")
- }
body, err := io.ReadAll(r.Body)
if err != nil {
@@ -174,7 +167,8 @@ func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []
}
r.Body = io.NopCloser(bytes.NewBuffer(body))
- conn, rw, err := hijacker.Hijack()
+ rc := http.NewResponseController(w)
+ conn, rw, err := rc.Hijack()
if err != nil {
return nil, nil, err
}
diff --git a/http2/http2.go b/http2/http2.go
index c7601c909f..6c18ea230b 100644
--- a/http2/http2.go
+++ b/http2/http2.go
@@ -34,11 +34,19 @@ import (
)
var (
- VerboseLogs bool
- logFrameWrites bool
- logFrameReads bool
- inTests bool
- disableExtendedConnectProtocol bool
+ VerboseLogs bool
+ logFrameWrites bool
+ logFrameReads bool
+ inTests bool
+
+ // Enabling extended CONNECT by causes browsers to attempt to use
+ // WebSockets-over-HTTP/2. This results in problems when the server's websocket
+ // package doesn't support extended CONNECT.
+ //
+ // Disable extended CONNECT by default for now.
+ //
+ // Issue #71128.
+ disableExtendedConnectProtocol = true
)
func init() {
@@ -51,8 +59,8 @@ func init() {
logFrameWrites = true
logFrameReads = true
}
- if strings.Contains(e, "http2xconnect=0") {
- disableExtendedConnectProtocol = true
+ if strings.Contains(e, "http2xconnect=1") {
+ disableExtendedConnectProtocol = false
}
}
@@ -407,23 +415,6 @@ func (s *sorter) SortStrings(ss []string) {
s.v = save
}
-// validPseudoPath reports whether v is a valid :path pseudo-header
-// value. It must be either:
-//
-// - a non-empty string starting with '/'
-// - the string '*', for OPTIONS requests.
-//
-// For now this is only used a quick check for deciding when to clean
-// up Opaque URLs before sending requests from the Transport.
-// See golang.org/issue/16847
-//
-// We used to enforce that the path also didn't start with "//", but
-// Google's GFE accepts such paths and Chrome sends them, so ignore
-// that part of the spec. See golang.org/issue/19103.
-func validPseudoPath(v string) bool {
- return (len(v) > 0 && v[0] == '/') || v == "*"
-}
-
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
diff --git a/http2/http2_test.go b/http2/http2_test.go
index b1e71f1532..c7774133a7 100644
--- a/http2/http2_test.go
+++ b/http2/http2_test.go
@@ -284,6 +284,15 @@ func TestNoUnicodeStrings(t *testing.T) {
}
}
+// setForTest sets *p = v, and restores its original value in t.Cleanup.
+func setForTest[T any](t *testing.T, p *T, v T) {
+ orig := *p
+ t.Cleanup(func() {
+ *p = orig
+ })
+ *p = v
+}
+
// must returns v if err is nil, or panics otherwise.
func must[T any](v T, err error) T {
if err != nil {
diff --git a/http2/netconn_test.go b/http2/netconn_test.go
index 0f1b5fb1f3..5a1759579e 100644
--- a/http2/netconn_test.go
+++ b/http2/netconn_test.go
@@ -76,7 +76,7 @@ func (c *synctestNetConn) Write(b []byte) (n int, err error) {
return c.rem.write(b)
}
-// IsClosed reports whether the peer has closed its end of the connection.
+// IsClosedByPeer reports whether the peer has closed its end of the connection.
func (c *synctestNetConn) IsClosedByPeer() bool {
if c.autoWait {
c.group.Wait()
diff --git a/http2/server.go b/http2/server.go
index b55547aec6..51fca38f61 100644
--- a/http2/server.go
+++ b/http2/server.go
@@ -50,6 +50,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
+ "golang.org/x/net/internal/httpcommon"
)
const (
@@ -812,8 +813,7 @@ const maxCachedCanonicalHeadersKeysSize = 2048
func (sc *serverConn) canonicalHeader(v string) string {
sc.serveG.check()
- buildCommonHeaderMapsOnce()
- cv, ok := commonCanonHeader[v]
+ cv, ok := httpcommon.CachedCanonicalHeader(v)
if ok {
return cv
}
@@ -1068,7 +1068,10 @@ func (sc *serverConn) serve(conf http2Config) {
func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) {
if sc.pingSent {
- sc.vlogf("timeout waiting for PING response")
+ sc.logf("timeout waiting for PING response")
+ if f := sc.countErrorFunc; f != nil {
+ f("conn_close_lost_ping")
+ }
sc.conn.Close()
return
}
@@ -2233,25 +2236,25 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) {
sc.serveG.check()
- rp := requestParam{
- method: f.PseudoValue("method"),
- scheme: f.PseudoValue("scheme"),
- authority: f.PseudoValue("authority"),
- path: f.PseudoValue("path"),
- protocol: f.PseudoValue("protocol"),
+ rp := httpcommon.ServerRequestParam{
+ Method: f.PseudoValue("method"),
+ Scheme: f.PseudoValue("scheme"),
+ Authority: f.PseudoValue("authority"),
+ Path: f.PseudoValue("path"),
+ Protocol: f.PseudoValue("protocol"),
}
// extended connect is disabled, so we should not see :protocol
- if disableExtendedConnectProtocol && rp.protocol != "" {
+ if disableExtendedConnectProtocol && rp.Protocol != "" {
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
- isConnect := rp.method == "CONNECT"
+ isConnect := rp.Method == "CONNECT"
if isConnect {
- if rp.protocol == "" && (rp.path != "" || rp.scheme != "" || rp.authority == "") {
+ if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") {
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
- } else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
+ } else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") {
// See 8.1.2.6 Malformed Requests and Responses:
//
// Malformed requests or responses that are detected
@@ -2265,15 +2268,16 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
}
- rp.header = make(http.Header)
+ header := make(http.Header)
+ rp.Header = header
for _, hf := range f.RegularFields() {
- rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value)
+ header.Add(sc.canonicalHeader(hf.Name), hf.Value)
}
- if rp.authority == "" {
- rp.authority = rp.header.Get("Host")
+ if rp.Authority == "" {
+ rp.Authority = header.Get("Host")
}
- if rp.protocol != "" {
- rp.header.Set(":protocol", rp.protocol)
+ if rp.Protocol != "" {
+ header.Set(":protocol", rp.Protocol)
}
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
@@ -2282,7 +2286,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
}
bodyOpen := !f.StreamEnded()
if bodyOpen {
- if vv, ok := rp.header["Content-Length"]; ok {
+ if vv, ok := rp.Header["Content-Length"]; ok {
if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil {
req.ContentLength = int64(cl)
} else {
@@ -2298,84 +2302,38 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
return rw, req, nil
}
-type requestParam struct {
- method string
- scheme, authority, path string
- protocol string
- header http.Header
-}
-
-func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*responseWriter, *http.Request, error) {
+func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) {
sc.serveG.check()
var tlsState *tls.ConnectionState // nil if not scheme https
- if rp.scheme == "https" {
+ if rp.Scheme == "https" {
tlsState = sc.tlsState
}
- needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
- if needsContinue {
- rp.header.Del("Expect")
- }
- // Merge Cookie headers into one "; "-delimited value.
- if cookies := rp.header["Cookie"]; len(cookies) > 1 {
- rp.header.Set("Cookie", strings.Join(cookies, "; "))
- }
-
- // Setup Trailers
- var trailer http.Header
- for _, v := range rp.header["Trailer"] {
- for _, key := range strings.Split(v, ",") {
- key = http.CanonicalHeaderKey(textproto.TrimString(key))
- switch key {
- case "Transfer-Encoding", "Trailer", "Content-Length":
- // Bogus. (copy of http1 rules)
- // Ignore.
- default:
- if trailer == nil {
- trailer = make(http.Header)
- }
- trailer[key] = nil
- }
- }
- }
- delete(rp.header, "Trailer")
-
- var url_ *url.URL
- var requestURI string
- if rp.method == "CONNECT" && rp.protocol == "" {
- url_ = &url.URL{Host: rp.authority}
- requestURI = rp.authority // mimic HTTP/1 server behavior
- } else {
- var err error
- url_, err = url.ParseRequestURI(rp.path)
- if err != nil {
- return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol))
- }
- requestURI = rp.path
+ res := httpcommon.NewServerRequest(rp)
+ if res.InvalidReason != "" {
+ return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol))
}
body := &requestBody{
conn: sc,
stream: st,
- needsContinue: needsContinue,
+ needsContinue: res.NeedsContinue,
}
- req := &http.Request{
- Method: rp.method,
- URL: url_,
+ req := (&http.Request{
+ Method: rp.Method,
+ URL: res.URL,
RemoteAddr: sc.remoteAddrStr,
- Header: rp.header,
- RequestURI: requestURI,
+ Header: rp.Header,
+ RequestURI: res.RequestURI,
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
TLS: tlsState,
- Host: rp.authority,
+ Host: rp.Authority,
Body: body,
- Trailer: trailer,
- }
- req = req.WithContext(st.ctx)
-
+ Trailer: res.Trailer,
+ }).WithContext(st.ctx)
rw := sc.newResponseWriter(st, req)
return rw, req, nil
}
@@ -3270,12 +3228,12 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
// we start in "half closed (remote)" for simplicity.
// See further comments at the definition of stateHalfClosedRemote.
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote)
- rw, req, err := sc.newWriterAndRequestNoBody(promised, requestParam{
- method: msg.method,
- scheme: msg.url.Scheme,
- authority: msg.url.Host,
- path: msg.url.RequestURI(),
- header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
+ rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
+ Method: msg.method,
+ Scheme: msg.url.Scheme,
+ Authority: msg.url.Host,
+ Path: msg.url.RequestURI(),
+ Header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
})
if err != nil {
// Should not happen, since we've already validated msg.url.
diff --git a/http2/server_test.go b/http2/server_test.go
index 201cf0d00e..b27a127a5e 100644
--- a/http2/server_test.go
+++ b/http2/server_test.go
@@ -1032,6 +1032,26 @@ func TestServer_Request_Reject_Pseudo_Unknown(t *testing.T) {
})
}
+func TestServer_Request_Reject_Authority_Userinfo(t *testing.T) {
+ // "':authority' MUST NOT include the deprecated userinfo subcomponent
+ // for "http" or "https" schemed URIs."
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
+ testRejectRequest(t, func(st *serverTester) {
+ var buf bytes.Buffer
+ enc := hpack.NewEncoder(&buf)
+ enc.WriteField(hpack.HeaderField{Name: ":authority", Value: "userinfo@example.tld"})
+ enc.WriteField(hpack.HeaderField{Name: ":method", Value: "GET"})
+ enc.WriteField(hpack.HeaderField{Name: ":path", Value: "/"})
+ enc.WriteField(hpack.HeaderField{Name: ":scheme", Value: "https"})
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: buf.Bytes(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ })
+}
+
func testRejectRequest(t *testing.T, send func(*serverTester)) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
t.Error("server request made it to handler; should've been rejected")
@@ -2794,6 +2814,8 @@ func testServerWritesTrailers(t *testing.T, withFlush bool) {
w.Header().Set("Trailer", "should not be included; Forbidden by RFC 7230 4.1.2")
return nil
}, func(st *serverTester) {
+ // Ignore errors from writing invalid trailers.
+ st.h1server.ErrorLog = log.New(io.Discard, "", 0)
getSlash(st)
st.wantHeaders(wantHeader{
streamID: 1,
@@ -4327,7 +4349,7 @@ func TestCanonicalHeaderCacheGrowth(t *testing.T) {
}
}
-// TestServerWriteDoesNotRetainBufferAfterStreamClose checks for access to
+// TestServerWriteDoesNotRetainBufferAfterReturn checks for access to
// the slice passed to ResponseWriter.Write after Write returns.
//
// Terminating the request stream on the client causes Write to return.
diff --git a/http2/sync_test.go b/http2/sync_test.go
index aeddbd6f3c..6687202d2c 100644
--- a/http2/sync_test.go
+++ b/http2/sync_test.go
@@ -24,9 +24,10 @@ type synctestGroup struct {
}
type goroutine struct {
- id int
- parent int
- state string
+ id int
+ parent int
+ state string
+ syscall bool
}
// newSynctest creates a new group with the synthetic clock set the provided time.
@@ -76,6 +77,14 @@ func (g *synctestGroup) Wait() {
return
}
runtime.Gosched()
+ if runtime.GOOS == "js" {
+ // When GOOS=js, we appear to need to time.Sleep to make progress
+ // on some syscalls. In particular, without this sleep
+ // writing to stdout (including via t.Log) can block forever.
+ for range 10 {
+ time.Sleep(1)
+ }
+ }
}
}
@@ -87,6 +96,9 @@ func (g *synctestGroup) idle() bool {
if !g.gids[gr.id] && !g.gids[gr.parent] {
continue
}
+ if gr.syscall {
+ return false
+ }
// From runtime/runtime2.go.
switch gr.state {
case "IO wait":
@@ -97,9 +109,6 @@ func (g *synctestGroup) idle() bool {
case "chan receive":
case "chan send":
case "sync.Cond.Wait":
- case "sync.Mutex.Lock":
- case "sync.RWMutex.RLock":
- case "sync.RWMutex.Lock":
default:
return false
}
@@ -138,6 +147,10 @@ func stacks(all bool) []goroutine {
panic(fmt.Errorf("3 unparsable goroutine stack:\n%s", gs))
}
state, rest, ok := strings.Cut(rest, "]")
+ isSyscall := false
+ if strings.Contains(rest, "\nsyscall.") {
+ isSyscall = true
+ }
var parent int
_, rest, ok = strings.Cut(rest, "\ncreated by ")
if ok && strings.Contains(rest, " in goroutine ") {
@@ -155,9 +168,10 @@ func stacks(all bool) []goroutine {
}
}
goroutines = append(goroutines, goroutine{
- id: id,
- parent: parent,
- state: state,
+ id: id,
+ parent: parent,
+ state: state,
+ syscall: isSyscall,
})
}
return goroutines
@@ -291,3 +305,25 @@ func (tm *fakeTimer) Stop() bool {
delete(tm.g.timers, tm)
return stopped
}
+
+// TestSynctestLogs verifies that t.Log works,
+// in particular that the GOOS=js workaround in synctestGroup.Wait is working.
+// (When GOOS=js, writing to stdout can hang indefinitely if some goroutine loops
+// calling runtime.Gosched; see Wait for the workaround.)
+func TestSynctestLogs(t *testing.T) {
+ g := newSynctest(time.Now())
+ donec := make(chan struct{})
+ go func() {
+ g.Join()
+ for range 100 {
+ t.Logf("logging a long line")
+ }
+ close(donec)
+ }()
+ g.Wait()
+ select {
+ case <-donec:
+ default:
+ panic("done")
+ }
+}
diff --git a/http2/transport.go b/http2/transport.go
index 090d0e1bdb..f26356b9cd 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -25,7 +25,6 @@ import (
"net/http"
"net/http/httptrace"
"net/textproto"
- "sort"
"strconv"
"strings"
"sync"
@@ -35,6 +34,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/idna"
+ "golang.org/x/net/internal/httpcommon"
)
const (
@@ -375,6 +375,7 @@ type ClientConn struct {
doNotReuse bool // whether conn is marked to not be reused for any future requests
closing bool
closed bool
+ closedOnIdle bool // true if conn was closed for idleness
seenSettings bool // true if we've seen a settings frame, false otherwise
seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
@@ -1089,10 +1090,12 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
// If this connection has never been used for a request and is closed,
// then let it take a request (which will fail).
+ // If the conn was closed for idleness, we're racing the idle timer;
+ // don't try to use the conn. (Issue #70515.)
//
// This avoids a situation where an error early in a connection's lifetime
// goes unreported.
- if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed {
+ if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle {
st.canTakeNewRequest = true
}
@@ -1155,6 +1158,7 @@ func (cc *ClientConn) closeIfIdle() {
return
}
cc.closed = true
+ cc.closedOnIdle = true
nextID := cc.nextStreamID
// TODO: do clients send GOAWAY too? maybe? Just Close:
cc.mu.Unlock()
@@ -1271,23 +1275,6 @@ func (cc *ClientConn) closeForLostPing() {
// exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests.
var errRequestCanceled = errors.New("net/http: request canceled")
-func commaSeparatedTrailers(req *http.Request) (string, error) {
- keys := make([]string, 0, len(req.Trailer))
- for k := range req.Trailer {
- k = canonicalHeader(k)
- switch k {
- case "Transfer-Encoding", "Trailer", "Content-Length":
- return "", fmt.Errorf("invalid Trailer key %q", k)
- }
- keys = append(keys, k)
- }
- if len(keys) > 0 {
- sort.Strings(keys)
- return strings.Join(keys, ","), nil
- }
- return "", nil
-}
-
func (cc *ClientConn) responseHeaderTimeout() time.Duration {
if cc.t.t1 != nil {
return cc.t.t1.ResponseHeaderTimeout
@@ -1299,22 +1286,6 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration {
return 0
}
-// checkConnHeaders checks whether req has any invalid connection-level headers.
-// per RFC 7540 section 8.1.2.2: Connection-Specific Header Fields.
-// Certain headers are special-cased as okay but not transmitted later.
-func checkConnHeaders(req *http.Request) error {
- if v := req.Header.Get("Upgrade"); v != "" {
- return fmt.Errorf("http2: invalid Upgrade request header: %q", req.Header["Upgrade"])
- }
- if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
- return fmt.Errorf("http2: invalid Transfer-Encoding request header: %q", vv)
- }
- if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
- return fmt.Errorf("http2: invalid Connection request header: %q", vv)
- }
- return nil
-}
-
// actualContentLength returns a sanitized version of
// req.ContentLength, where 0 actually means zero (not unknown) and -1
// means unknown.
@@ -1360,25 +1331,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
donec: make(chan struct{}),
}
- // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
- if !cc.t.disableCompression() &&
- req.Header.Get("Accept-Encoding") == "" &&
- req.Header.Get("Range") == "" &&
- !cs.isHead {
- // Request gzip only, not deflate. Deflate is ambiguous and
- // not as universally supported anyway.
- // See: https://zlib.net/zlib_faq.html#faq39
- //
- // Note that we don't request this for HEAD requests,
- // due to a bug in nginx:
- // http://trac.nginx.org/nginx/ticket/358
- // https://golang.org/issue/5522
- //
- // We don't request gzip if the request is for a range, since
- // auto-decoding a portion of a gzipped document will just fail
- // anyway. See https://golang.org/issue/8923
- cs.requestedGzip = true
- }
+ cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression())
go cs.doRequest(req, streamf)
@@ -1492,10 +1445,6 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre
cc := cs.cc
ctx := cs.ctx
- if err := checkConnHeaders(req); err != nil {
- return err
- }
-
// wait for setting frames to be received, a server can change this value later,
// but we just wait for the first settings frame
var isExtendedConnect bool
@@ -1659,26 +1608,39 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error {
// we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is
// sent by writeRequestBody below, along with any Trailers,
// again in form HEADERS{1}, CONTINUATION{0,})
- trailers, err := commaSeparatedTrailers(req)
- if err != nil {
- return err
- }
- hasTrailers := trailers != ""
- contentLen := actualContentLength(req)
- hasBody := contentLen != 0
- hdrs, err := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen)
+ cc.hbuf.Reset()
+ res, err := encodeRequestHeaders(req, cs.requestedGzip, cc.peerMaxHeaderListSize, func(name, value string) {
+ cc.writeHeader(name, value)
+ })
if err != nil {
- return err
+ return fmt.Errorf("http2: %w", err)
}
+ hdrs := cc.hbuf.Bytes()
// Write the request.
- endStream := !hasBody && !hasTrailers
+ endStream := !res.HasBody && !res.HasTrailers
cs.sentHeaders = true
err = cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
traceWroteHeaders(cs.trace)
return err
}
+func encodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) {
+ return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
+ Request: httpcommon.Request{
+ Header: req.Header,
+ Trailer: req.Trailer,
+ URL: req.URL,
+ Host: req.Host,
+ Method: req.Method,
+ ActualContentLength: actualContentLength(req),
+ },
+ AddGzipHeader: addGzipHeader,
+ PeerMaxHeaderListSize: peerMaxHeaderListSize,
+ DefaultUserAgent: defaultUserAgent,
+ }, headerf)
+}
+
// cleanupWriteRequest performs post-request tasks.
//
// If err (the result of writeRequest) is non-nil and the stream is not closed,
@@ -2066,218 +2028,6 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
}
}
-func validateHeaders(hdrs http.Header) string {
- for k, vv := range hdrs {
- if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
- return fmt.Sprintf("name %q", k)
- }
- for _, v := range vv {
- if !httpguts.ValidHeaderFieldValue(v) {
- // Don't include the value in the error,
- // because it may be sensitive.
- return fmt.Sprintf("value for header %q", k)
- }
- }
- }
- return ""
-}
-
-var errNilRequestURL = errors.New("http2: Request.URI is nil")
-
-func isNormalConnect(req *http.Request) bool {
- return req.Method == "CONNECT" && req.Header.Get(":protocol") == ""
-}
-
-// requires cc.wmu be held.
-func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
- cc.hbuf.Reset()
- if req.URL == nil {
- return nil, errNilRequestURL
- }
-
- host := req.Host
- if host == "" {
- host = req.URL.Host
- }
- host, err := httpguts.PunycodeHostPort(host)
- if err != nil {
- return nil, err
- }
- if !httpguts.ValidHostHeader(host) {
- return nil, errors.New("http2: invalid Host header")
- }
-
- var path string
- if !isNormalConnect(req) {
- path = req.URL.RequestURI()
- if !validPseudoPath(path) {
- orig := path
- path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
- if !validPseudoPath(path) {
- if req.URL.Opaque != "" {
- return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
- } else {
- return nil, fmt.Errorf("invalid request :path %q", orig)
- }
- }
- }
- }
-
- // Check for any invalid headers+trailers and return an error before we
- // potentially pollute our hpack state. (We want to be able to
- // continue to reuse the hpack encoder for future requests)
- if err := validateHeaders(req.Header); err != "" {
- return nil, fmt.Errorf("invalid HTTP header %s", err)
- }
- if err := validateHeaders(req.Trailer); err != "" {
- return nil, fmt.Errorf("invalid HTTP trailer %s", err)
- }
-
- enumerateHeaders := func(f func(name, value string)) {
- // 8.1.2.3 Request Pseudo-Header Fields
- // The :path pseudo-header field includes the path and query parts of the
- // target URI (the path-absolute production and optionally a '?' character
- // followed by the query production, see Sections 3.3 and 3.4 of
- // [RFC3986]).
- f(":authority", host)
- m := req.Method
- if m == "" {
- m = http.MethodGet
- }
- f(":method", m)
- if !isNormalConnect(req) {
- f(":path", path)
- f(":scheme", req.URL.Scheme)
- }
- if trailers != "" {
- f("trailer", trailers)
- }
-
- var didUA bool
- for k, vv := range req.Header {
- if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
- // Host is :authority, already sent.
- // Content-Length is automatic, set below.
- continue
- } else if asciiEqualFold(k, "connection") ||
- asciiEqualFold(k, "proxy-connection") ||
- asciiEqualFold(k, "transfer-encoding") ||
- asciiEqualFold(k, "upgrade") ||
- asciiEqualFold(k, "keep-alive") {
- // Per 8.1.2.2 Connection-Specific Header
- // Fields, don't send connection-specific
- // fields. We have already checked if any
- // are error-worthy so just ignore the rest.
- continue
- } else if asciiEqualFold(k, "user-agent") {
- // Match Go's http1 behavior: at most one
- // User-Agent. If set to nil or empty string,
- // then omit it. Otherwise if not mentioned,
- // include the default (below).
- didUA = true
- if len(vv) < 1 {
- continue
- }
- vv = vv[:1]
- if vv[0] == "" {
- continue
- }
- } else if asciiEqualFold(k, "cookie") {
- // Per 8.1.2.5 To allow for better compression efficiency, the
- // Cookie header field MAY be split into separate header fields,
- // each with one or more cookie-pairs.
- for _, v := range vv {
- for {
- p := strings.IndexByte(v, ';')
- if p < 0 {
- break
- }
- f("cookie", v[:p])
- p++
- // strip space after semicolon if any.
- for p+1 <= len(v) && v[p] == ' ' {
- p++
- }
- v = v[p:]
- }
- if len(v) > 0 {
- f("cookie", v)
- }
- }
- continue
- }
-
- for _, v := range vv {
- f(k, v)
- }
- }
- if shouldSendReqContentLength(req.Method, contentLength) {
- f("content-length", strconv.FormatInt(contentLength, 10))
- }
- if addGzipHeader {
- f("accept-encoding", "gzip")
- }
- if !didUA {
- f("user-agent", defaultUserAgent)
- }
- }
-
- // Do a first pass over the headers counting bytes to ensure
- // we don't exceed cc.peerMaxHeaderListSize. This is done as a
- // separate pass before encoding the headers to prevent
- // modifying the hpack state.
- hlSize := uint64(0)
- enumerateHeaders(func(name, value string) {
- hf := hpack.HeaderField{Name: name, Value: value}
- hlSize += uint64(hf.Size())
- })
-
- if hlSize > cc.peerMaxHeaderListSize {
- return nil, errRequestHeaderListSize
- }
-
- trace := httptrace.ContextClientTrace(req.Context())
- traceHeaders := traceHasWroteHeaderField(trace)
-
- // Header list size is ok. Write the headers.
- enumerateHeaders(func(name, value string) {
- name, ascii := lowerHeader(name)
- if !ascii {
- // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
- // field names have to be ASCII characters (just as in HTTP/1.x).
- return
- }
- cc.writeHeader(name, value)
- if traceHeaders {
- traceWroteHeaderField(trace, name, value)
- }
- })
-
- return cc.hbuf.Bytes(), nil
-}
-
-// shouldSendReqContentLength reports whether the http2.Transport should send
-// a "content-length" request header. This logic is basically a copy of the net/http
-// transferWriter.shouldSendContentLength.
-// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
-// -1 means unknown.
-func shouldSendReqContentLength(method string, contentLength int64) bool {
- if contentLength > 0 {
- return true
- }
- if contentLength < 0 {
- return false
- }
- // For zero bodies, whether we send a content-length depends on the method.
- // It also kinda doesn't matter for http2 either way, with END_STREAM.
- switch method {
- case "POST", "PUT", "PATCH":
- return true
- default:
- return false
- }
-}
-
// requires cc.wmu be held.
func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
cc.hbuf.Reset()
@@ -2294,7 +2044,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
}
for k, vv := range trailer {
- lowKey, ascii := lowerHeader(k)
+ lowKey, ascii := httpcommon.LowerHeader(k)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
@@ -2434,9 +2184,12 @@ func (rl *clientConnReadLoop) cleanup() {
// This avoids a situation where new connections are constantly created,
// added to the pool, fail, and are removed from the pool, without any error
// being surfaced to the user.
- const unusedWaitTime = 5 * time.Second
+ unusedWaitTime := 5 * time.Second
+ if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout {
+ unusedWaitTime = cc.idleTimeout
+ }
idleTime := cc.t.now().Sub(cc.lastActive)
- if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime {
+ if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle {
cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() {
cc.t.connPool().MarkDead(cc)
})
@@ -2457,6 +2210,13 @@ func (rl *clientConnReadLoop) cleanup() {
}
cc.cond.Broadcast()
cc.mu.Unlock()
+
+ if !cc.seenSettings {
+ // If we have a pending request that wants extended CONNECT,
+ // let it continue and fail with the connection error.
+ cc.extendedConnectAllowed = true
+ close(cc.seenSettingsChan)
+ }
}
// countReadFrameError calls Transport.CountError with a string
@@ -2549,9 +2309,6 @@ func (rl *clientConnReadLoop) run() error {
if VerboseLogs {
cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, summarizeFrame(f), err)
}
- if !cc.seenSettings {
- close(cc.seenSettingsChan)
- }
return err
}
}
@@ -2646,7 +2403,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
Status: status + " " + http.StatusText(statusCode),
}
for _, hf := range regularFields {
- key := canonicalHeader(hf.Name)
+ key := httpcommon.CanonicalHeader(hf.Name)
if key == "Trailer" {
t := res.Trailer
if t == nil {
@@ -2654,7 +2411,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
res.Trailer = t
}
foreachHeaderElement(hf.Value, func(v string) {
- t[canonicalHeader(v)] = nil
+ t[httpcommon.CanonicalHeader(v)] = nil
})
} else {
vv := header[key]
@@ -2778,7 +2535,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr
trailer := make(http.Header)
for _, hf := range f.RegularFields() {
- key := canonicalHeader(hf.Name)
+ key := httpcommon.CanonicalHeader(hf.Name)
trailer[key] = append(trailer[key], hf.Value)
}
cs.trailer = trailer
@@ -3324,7 +3081,7 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, ping bool,
var (
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
- errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit")
+ errRequestHeaderListSize = httpcommon.ErrRequestHeaderListSize
)
func (cc *ClientConn) logf(format string, args ...interface{}) {
@@ -3508,16 +3265,6 @@ func traceFirstResponseByte(trace *httptrace.ClientTrace) {
}
}
-func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
- return trace != nil && trace.WroteHeaderField != nil
-}
-
-func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
- if trace != nil && trace.WroteHeaderField != nil {
- trace.WroteHeaderField(k, []string{v})
- }
-}
-
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
diff --git a/http2/transport_test.go b/http2/transport_test.go
index 0e12e0f1c7..596499f3ea 100644
--- a/http2/transport_test.go
+++ b/http2/transport_test.go
@@ -272,6 +272,48 @@ func TestTransport(t *testing.T) {
}
}
+func TestTransportFailureErrorForHTTP1Response(t *testing.T) {
+ const expectedHTTP1PayloadHint = "frame header looked like an HTTP/1.1 header"
+
+ ts := httptest.NewServer(http.NewServeMux())
+ t.Cleanup(ts.Close)
+
+ for _, tc := range []struct {
+ name string
+ maxFrameSize uint32
+ expectedErrorIs error
+ }{
+ {
+ name: "with default max frame size",
+ maxFrameSize: 0,
+ },
+ {
+ name: "with enough frame size to start reading",
+ maxFrameSize: invalidHTTP1LookingFrameHeader().Length + 1,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ tr := &Transport{
+ DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
+ return net.Dial(network, addr)
+ },
+ MaxReadFrameSize: tc.maxFrameSize,
+ AllowHTTP: true,
+ }
+
+ req, err := http.NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = tr.RoundTrip(req)
+ if !strings.Contains(err.Error(), expectedHTTP1PayloadHint) {
+ t.Errorf("expected error to contain %q, got %v", expectedHTTP1PayloadHint, err)
+ }
+ })
+ }
+}
+
func testTransportReusesConns(t *testing.T, useClient, wantSame bool, modReq func(*http.Request)) {
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.RemoteAddr)
@@ -1420,7 +1462,7 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) {
res0.Body.Close()
res, err := tr.RoundTrip(req)
- if err != wantErr {
+ if !errors.Is(err, wantErr) {
if res != nil {
res.Body.Close()
}
@@ -1443,26 +1485,14 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) {
}
}
headerListSizeForRequest := func(req *http.Request) (size uint64) {
- contentLen := actualContentLength(req)
- trailers, err := commaSeparatedTrailers(req)
- if err != nil {
- t.Fatalf("headerListSizeForRequest: %v", err)
- }
- cc := &ClientConn{peerMaxHeaderListSize: 0xffffffffffffffff}
- cc.henc = hpack.NewEncoder(&cc.hbuf)
- cc.mu.Lock()
- hdrs, err := cc.encodeHeaders(req, true, trailers, contentLen)
- cc.mu.Unlock()
- if err != nil {
- t.Fatalf("headerListSizeForRequest: %v", err)
- }
- hpackDec := hpack.NewDecoder(initialHeaderTableSize, func(hf hpack.HeaderField) {
+ const addGzipHeader = true
+ const peerMaxHeaderListSize = 0xffffffffffffffff
+ _, err := encodeRequestHeaders(req, addGzipHeader, peerMaxHeaderListSize, func(name, value string) {
+ hf := hpack.HeaderField{Name: name, Value: value}
size += uint64(hf.Size())
})
- if len(hdrs) > 0 {
- if _, err := hpackDec.Write(hdrs); err != nil {
- t.Fatalf("headerListSizeForRequest: %v", err)
- }
+ if err != nil {
+ t.Fatal(err)
}
return size
}
@@ -2853,11 +2883,15 @@ func TestTransportRequestPathPseudo(t *testing.T) {
},
}
for i, tt := range tests {
- cc := &ClientConn{peerMaxHeaderListSize: 0xffffffffffffffff}
- cc.henc = hpack.NewEncoder(&cc.hbuf)
- cc.mu.Lock()
- hdrs, err := cc.encodeHeaders(tt.req, false, "", -1)
- cc.mu.Unlock()
+ hbuf := &bytes.Buffer{}
+ henc := hpack.NewEncoder(hbuf)
+
+ const addGzipHeader = false
+ const peerMaxHeaderListSize = 0xffffffffffffffff
+ _, err := encodeRequestHeaders(tt.req, addGzipHeader, peerMaxHeaderListSize, func(name, value string) {
+ henc.WriteField(hpack.HeaderField{Name: name, Value: value})
+ })
+ hdrs := hbuf.Bytes()
var got result
hpackDec := hpack.NewDecoder(initialHeaderTableSize, func(f hpack.HeaderField) {
if f.Name == ":path" {
@@ -5789,6 +5823,38 @@ func TestTransportTLSNextProtoConnImmediateFailureUsed(t *testing.T) {
}
}
+// Test the case where a conn provided via a TLSNextProto hook is closed for idleness
+// before we use it.
+func TestTransportTLSNextProtoConnIdleTimoutBeforeUse(t *testing.T) {
+ t1 := &http.Transport{
+ IdleConnTimeout: 1 * time.Second,
+ }
+ t2, _ := ConfigureTransports(t1)
+ tt := newTestTransport(t, t2)
+
+ // Create a new, fake connection and pass it to the Transport via the TLSNextProto hook.
+ cli, _ := synctestNetPipe(tt.group)
+ cliTLS := tls.Client(cli, tlsConfigInsecure)
+ go func() {
+ tt.group.Join()
+ t1.TLSNextProto["h2"]("dummy.tld", cliTLS)
+ }()
+ tt.sync()
+ tc := tt.getConn()
+
+ // The connection encounters an error before we send a request that uses it.
+ tc.advance(2 * time.Second)
+
+ // Send a request on the Transport.
+ //
+ // It should fail with ErrNoCachedConn.
+ req := must(http.NewRequest("GET", "https://dummy.tld/", nil))
+ rt := tt.roundTrip(req)
+ if err := rt.err(); !errors.Is(err, ErrNoCachedConn) {
+ t.Fatalf("RoundTrip with conn closed for idleness: got %v, want ErrNoCachedConn", err)
+ }
+}
+
// Test the case where a conn provided via a TLSNextProto hook immediately encounters an error,
// but no requests are sent which would use the bad connection.
func TestTransportTLSNextProtoConnImmediateFailureUnused(t *testing.T) {
@@ -5824,7 +5890,7 @@ func TestTransportTLSNextProtoConnImmediateFailureUnused(t *testing.T) {
}
func TestExtendedConnectClientWithServerSupport(t *testing.T) {
- disableExtendedConnectProtocol = false
+ setForTest(t, &disableExtendedConnectProtocol, false)
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(":protocol") != "extended-connect" {
t.Fatalf("unexpected :protocol header received")
@@ -5840,6 +5906,9 @@ func TestExtendedConnectClientWithServerSupport(t *testing.T) {
pwDone := make(chan struct{})
req, _ := http.NewRequest("CONNECT", ts.URL, pr)
req.Header.Set(":protocol", "extended-connect")
+ req.Header.Set("X-A", "A")
+ req.Header.Set("X-B", "B")
+ req.Header.Set("X-C", "C")
go func() {
pw.Write([]byte("hello, extended connect"))
pw.Close()
@@ -5860,7 +5929,7 @@ func TestExtendedConnectClientWithServerSupport(t *testing.T) {
}
func TestExtendedConnectClientWithoutServerSupport(t *testing.T) {
- disableExtendedConnectProtocol = true
+ setForTest(t, &disableExtendedConnectProtocol, true)
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
})
@@ -5873,6 +5942,9 @@ func TestExtendedConnectClientWithoutServerSupport(t *testing.T) {
pwDone := make(chan struct{})
req, _ := http.NewRequest("CONNECT", ts.URL, pr)
req.Header.Set(":protocol", "extended-connect")
+ req.Header.Set("X-A", "A")
+ req.Header.Set("X-B", "B")
+ req.Header.Set("X-C", "C")
go func() {
pw.Write([]byte("hello, extended connect"))
pw.Close()
@@ -5884,3 +5956,24 @@ func TestExtendedConnectClientWithoutServerSupport(t *testing.T) {
t.Fatalf("expected error errExtendedConnectNotSupported, got: %v", err)
}
}
+
+// Issue #70658: Make sure extended CONNECT requests don't get stuck if a
+// connection fails early in its lifetime.
+func TestExtendedConnectReadFrameError(t *testing.T) {
+ tc := newTestClientConn(t)
+ tc.wantFrameType(FrameSettings)
+ tc.wantFrameType(FrameWindowUpdate)
+
+ req, _ := http.NewRequest("CONNECT", "https://dummy.tld/", nil)
+ req.Header.Set(":protocol", "extended-connect")
+ rt := tc.roundTrip(req)
+ tc.wantIdle() // waiting for SETTINGS response
+
+ tc.closeWrite() // connection breaks without sending SETTINGS
+ if !rt.done() {
+ t.Fatalf("after connection closed: RoundTrip still running; want done")
+ }
+ if rt.err() == nil {
+ t.Fatalf("after connection closed: RoundTrip succeeded; want error")
+ }
+}
diff --git a/http2/write.go b/http2/write.go
index 6ff6bee7e9..fdb35b9477 100644
--- a/http2/write.go
+++ b/http2/write.go
@@ -13,6 +13,7 @@ import (
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
+ "golang.org/x/net/internal/httpcommon"
)
// writeFramer is implemented by any type that is used to write frames.
@@ -351,7 +352,7 @@ func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
}
for _, k := range keys {
vv := h[k]
- k, ascii := lowerHeader(k)
+ k, ascii := httpcommon.LowerHeader(k)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
diff --git a/http2/gate_test.go b/internal/gate/gate.go
similarity index 52%
rename from http2/gate_test.go
rename to internal/gate/gate.go
index e5e6a315be..5c026c002d 100644
--- a/http2/gate_test.go
+++ b/internal/gate/gate.go
@@ -1,40 +1,37 @@
// 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 http2
+
+// Package gate contains an alternative condition variable.
+package gate
import "context"
-// An gate is a monitor (mutex + condition variable) with one bit of state.
+// A Gate is a monitor (mutex + condition variable) with one bit of state.
//
// The condition may be either set or unset.
// Lock operations may be unconditional, or wait for the condition to be set.
// Unlock operations record the new state of the condition.
-type gate struct {
+type Gate struct {
// When unlocked, exactly one of set or unset contains a value.
// When locked, neither chan contains a value.
set chan struct{}
unset chan struct{}
}
-// newGate returns a new, unlocked gate with the condition unset.
-func newGate() gate {
- g := newLockedGate()
- g.unlock(false)
- return g
-}
-
-// newLocked gate returns a new, locked gate.
-func newLockedGate() gate {
- return gate{
+// New returns a new, unlocked gate.
+func New(set bool) Gate {
+ g := Gate{
set: make(chan struct{}, 1),
unset: make(chan struct{}, 1),
}
+ g.Unlock(set)
+ return g
}
-// lock acquires the gate unconditionally.
+// Lock acquires the gate unconditionally.
// It reports whether the condition is set.
-func (g *gate) lock() (set bool) {
+func (g *Gate) Lock() (set bool) {
select {
case <-g.set:
return true
@@ -43,9 +40,9 @@ func (g *gate) lock() (set bool) {
}
}
-// waitAndLock waits until the condition is set before acquiring the gate.
-// If the context expires, waitAndLock returns an error and does not acquire the gate.
-func (g *gate) waitAndLock(ctx context.Context) error {
+// WaitAndLock waits until the condition is set before acquiring the gate.
+// If the context expires, WaitAndLock returns an error and does not acquire the gate.
+func (g *Gate) WaitAndLock(ctx context.Context) error {
select {
case <-g.set:
return nil
@@ -59,8 +56,8 @@ func (g *gate) waitAndLock(ctx context.Context) error {
}
}
-// lockIfSet acquires the gate if and only if the condition is set.
-func (g *gate) lockIfSet() (acquired bool) {
+// LockIfSet acquires the gate if and only if the condition is set.
+func (g *Gate) LockIfSet() (acquired bool) {
select {
case <-g.set:
return true
@@ -69,17 +66,11 @@ func (g *gate) lockIfSet() (acquired bool) {
}
}
-// unlock sets the condition and releases the gate.
-func (g *gate) unlock(set bool) {
+// Unlock sets the condition and releases the gate.
+func (g *Gate) Unlock(set bool) {
if set {
g.set <- struct{}{}
} else {
g.unset <- struct{}{}
}
}
-
-// unlock sets the condition to the result of f and releases the gate.
-// Useful in defers.
-func (g *gate) unlockFunc(f func() bool) {
- g.unlock(f())
-}
diff --git a/internal/gate/gate_test.go b/internal/gate/gate_test.go
new file mode 100644
index 0000000000..87a78b15af
--- /dev/null
+++ b/internal/gate/gate_test.go
@@ -0,0 +1,85 @@
+// 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 gate_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "golang.org/x/net/internal/gate"
+)
+
+func TestGateLockAndUnlock(t *testing.T) {
+ g := gate.New(false)
+ if set := g.Lock(); set {
+ t.Errorf("g.Lock of never-locked gate: true, want false")
+ }
+ unlockedc := make(chan struct{})
+ donec := make(chan struct{})
+ go func() {
+ defer close(donec)
+ if set := g.Lock(); !set {
+ t.Errorf("g.Lock of set gate: false, want true")
+ }
+ select {
+ case <-unlockedc:
+ default:
+ t.Errorf("g.Lock succeeded while gate was held")
+ }
+ g.Unlock(false)
+ }()
+ time.Sleep(1 * time.Millisecond)
+ close(unlockedc)
+ g.Unlock(true)
+ <-donec
+ if set := g.Lock(); set {
+ t.Errorf("g.Lock of unset gate: true, want false")
+ }
+}
+
+func TestGateWaitAndLock(t *testing.T) {
+ g := gate.New(false)
+ // WaitAndLock is canceled.
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
+ defer cancel()
+ if err := g.WaitAndLock(ctx); err != context.DeadlineExceeded {
+ t.Fatalf("g.WaitAndLock = %v, want context.DeadlineExceeded", err)
+ }
+ // WaitAndLock succeeds.
+ set := false
+ go func() {
+ time.Sleep(1 * time.Millisecond)
+ g.Lock()
+ set = true
+ g.Unlock(true)
+ }()
+ if err := g.WaitAndLock(context.Background()); err != nil {
+ t.Fatalf("g.WaitAndLock = %v, want nil", err)
+ }
+ if !set {
+ t.Fatalf("g.WaitAndLock returned before gate was set")
+ }
+ g.Unlock(true)
+ // WaitAndLock succeeds when the gate is set and the context is canceled.
+ if err := g.WaitAndLock(ctx); err != nil {
+ t.Fatalf("g.WaitAndLock = %v, want nil", err)
+ }
+}
+
+func TestGateLockIfSet(t *testing.T) {
+ g := gate.New(false)
+ if locked := g.LockIfSet(); locked {
+ t.Fatalf("g.LockIfSet of unset gate = %v, want false", locked)
+ }
+ g.Lock()
+ if locked := g.LockIfSet(); locked {
+ t.Fatalf("g.LockIfSet of locked gate = %v, want false", locked)
+ }
+ g.Unlock(true)
+ if locked := g.LockIfSet(); !locked {
+ t.Fatalf("g.LockIfSet of set gate = %v, want true", locked)
+ }
+}
diff --git a/internal/http3/body.go b/internal/http3/body.go
new file mode 100644
index 0000000000..cdde482efb
--- /dev/null
+++ b/internal/http3/body.go
@@ -0,0 +1,142 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "sync"
+)
+
+// A bodyWriter writes a request or response body to a stream
+// as a series of DATA frames.
+type bodyWriter struct {
+ st *stream
+ remain int64 // -1 when content-length is not known
+ flush bool // flush the stream after every write
+ name string // "request" or "response"
+}
+
+func (w *bodyWriter) Write(p []byte) (n int, err error) {
+ if w.remain >= 0 && int64(len(p)) > w.remain {
+ return 0, &streamError{
+ code: errH3InternalError,
+ message: w.name + " body longer than specified content length",
+ }
+ }
+ w.st.writeVarint(int64(frameTypeData))
+ w.st.writeVarint(int64(len(p)))
+ n, err = w.st.Write(p)
+ if w.remain >= 0 {
+ w.remain -= int64(n)
+ }
+ if w.flush && err == nil {
+ err = w.st.Flush()
+ }
+ if err != nil {
+ err = fmt.Errorf("writing %v body: %w", w.name, err)
+ }
+ return n, err
+}
+
+func (w *bodyWriter) Close() error {
+ if w.remain > 0 {
+ return errors.New(w.name + " body shorter than specified content length")
+ }
+ return nil
+}
+
+// A bodyReader reads a request or response body from a stream.
+type bodyReader struct {
+ st *stream
+
+ mu sync.Mutex
+ remain int64
+ err error
+}
+
+func (r *bodyReader) Read(p []byte) (n int, err error) {
+ // The HTTP/1 and HTTP/2 implementations both permit concurrent reads from a body,
+ // in the sense that the race detector won't complain.
+ // Use a mutex here to provide the same behavior.
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.err != nil {
+ return 0, r.err
+ }
+ defer func() {
+ if err != nil {
+ r.err = err
+ }
+ }()
+ if r.st.lim == 0 {
+ // We've finished reading the previous DATA frame, so end it.
+ if err := r.st.endFrame(); err != nil {
+ return 0, err
+ }
+ }
+ // Read the next DATA frame header,
+ // if we aren't already in the middle of one.
+ for r.st.lim < 0 {
+ ftype, err := r.st.readFrameHeader()
+ if err == io.EOF && r.remain > 0 {
+ return 0, &streamError{
+ code: errH3MessageError,
+ message: "body shorter than content-length",
+ }
+ }
+ if err != nil {
+ return 0, err
+ }
+ switch ftype {
+ case frameTypeData:
+ if r.remain >= 0 && r.st.lim > r.remain {
+ return 0, &streamError{
+ code: errH3MessageError,
+ message: "body longer than content-length",
+ }
+ }
+ // Fall out of the loop and process the frame body below.
+ case frameTypeHeaders:
+ // This HEADERS frame contains the message trailers.
+ if r.remain > 0 {
+ return 0, &streamError{
+ code: errH3MessageError,
+ message: "body shorter than content-length",
+ }
+ }
+ // TODO: Fill in Request.Trailer.
+ if err := r.st.discardFrame(); err != nil {
+ return 0, err
+ }
+ return 0, io.EOF
+ default:
+ if err := r.st.discardUnknownFrame(ftype); err != nil {
+ return 0, err
+ }
+ }
+ }
+ // We are now reading the content of a DATA frame.
+ // Fill the read buffer or read to the end of the frame,
+ // whichever comes first.
+ if int64(len(p)) > r.st.lim {
+ p = p[:r.st.lim]
+ }
+ n, err = r.st.Read(p)
+ if r.remain > 0 {
+ r.remain -= int64(n)
+ }
+ return n, err
+}
+
+func (r *bodyReader) Close() error {
+ // Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written),
+ // calling Close concurrently with Read will interrupt the read.
+ r.st.stream.CloseRead()
+ return nil
+}
diff --git a/internal/http3/body_test.go b/internal/http3/body_test.go
new file mode 100644
index 0000000000..599e0df816
--- /dev/null
+++ b/internal/http3/body_test.go
@@ -0,0 +1,276 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "testing"
+)
+
+// TestReadData tests servers reading request bodies, and clients reading response bodies.
+func TestReadData(t *testing.T) {
+ // These tests consist of a series of steps,
+ // where each step is either something arriving on the stream
+ // or the client/server reading from the body.
+ type (
+ // HEADERS frame arrives (headers).
+ receiveHeaders struct {
+ contentLength int64 // -1 for no content-length
+ }
+ // DATA frame header arrives.
+ receiveDataHeader struct {
+ size int64
+ }
+ // DATA frame content arrives.
+ receiveData struct {
+ size int64
+ }
+ // HEADERS frame arrives (trailers).
+ receiveTrailers struct{}
+ // Some other frame arrives.
+ receiveFrame struct {
+ ftype frameType
+ data []byte
+ }
+ // Stream closed, ending the body.
+ receiveEOF struct{}
+ // Server reads from Request.Body, or client reads from Response.Body.
+ wantBody struct {
+ size int64
+ eof bool
+ }
+ wantError struct{}
+ )
+ for _, test := range []struct {
+ name string
+ respHeader http.Header
+ steps []any
+ wantError bool
+ }{{
+ name: "no content length",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveDataHeader{size: 10},
+ receiveData{size: 10},
+ receiveEOF{},
+ wantBody{size: 10, eof: true},
+ },
+ }, {
+ name: "valid content length",
+ steps: []any{
+ receiveHeaders{contentLength: 10},
+ receiveDataHeader{size: 10},
+ receiveData{size: 10},
+ receiveEOF{},
+ wantBody{size: 10, eof: true},
+ },
+ }, {
+ name: "data frame exceeds content length",
+ steps: []any{
+ receiveHeaders{contentLength: 5},
+ receiveDataHeader{size: 10},
+ receiveData{size: 10},
+ wantError{},
+ },
+ }, {
+ name: "data frame after all content read",
+ steps: []any{
+ receiveHeaders{contentLength: 5},
+ receiveDataHeader{size: 5},
+ receiveData{size: 5},
+ wantBody{size: 5},
+ receiveDataHeader{size: 1},
+ receiveData{size: 1},
+ wantError{},
+ },
+ }, {
+ name: "content length too long",
+ steps: []any{
+ receiveHeaders{contentLength: 10},
+ receiveDataHeader{size: 5},
+ receiveData{size: 5},
+ receiveEOF{},
+ wantBody{size: 5},
+ wantError{},
+ },
+ }, {
+ name: "stream ended by trailers",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveDataHeader{size: 5},
+ receiveData{size: 5},
+ receiveTrailers{},
+ wantBody{size: 5, eof: true},
+ },
+ }, {
+ name: "trailers and content length too long",
+ steps: []any{
+ receiveHeaders{contentLength: 10},
+ receiveDataHeader{size: 5},
+ receiveData{size: 5},
+ wantBody{size: 5},
+ receiveTrailers{},
+ wantError{},
+ },
+ }, {
+ name: "unknown frame before headers",
+ steps: []any{
+ receiveFrame{
+ ftype: 0x1f + 0x21, // reserved frame type
+ data: []byte{1, 2, 3, 4},
+ },
+ receiveHeaders{contentLength: -1},
+ receiveDataHeader{size: 10},
+ receiveData{size: 10},
+ wantBody{size: 10},
+ },
+ }, {
+ name: "unknown frame after headers",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveFrame{
+ ftype: 0x1f + 0x21, // reserved frame type
+ data: []byte{1, 2, 3, 4},
+ },
+ receiveDataHeader{size: 10},
+ receiveData{size: 10},
+ wantBody{size: 10},
+ },
+ }, {
+ name: "invalid frame",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveFrame{
+ ftype: frameTypeSettings, // not a valid frame on this stream
+ data: []byte{1, 2, 3, 4},
+ },
+ wantError{},
+ },
+ }, {
+ name: "data frame consumed by several reads",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveDataHeader{size: 16},
+ receiveData{size: 16},
+ wantBody{size: 2},
+ wantBody{size: 4},
+ wantBody{size: 8},
+ wantBody{size: 2},
+ },
+ }, {
+ name: "read multiple frames",
+ steps: []any{
+ receiveHeaders{contentLength: -1},
+ receiveDataHeader{size: 2},
+ receiveData{size: 2},
+ receiveDataHeader{size: 4},
+ receiveData{size: 4},
+ receiveDataHeader{size: 8},
+ receiveData{size: 8},
+ wantBody{size: 2},
+ wantBody{size: 4},
+ wantBody{size: 8},
+ },
+ }} {
+
+ runTest := func(t testing.TB, h http.Header, st *testQUICStream, body func() io.ReadCloser) {
+ var (
+ bytesSent int
+ bytesReceived int
+ )
+ for _, step := range test.steps {
+ switch step := step.(type) {
+ case receiveHeaders:
+ header := h.Clone()
+ if step.contentLength != -1 {
+ header["content-length"] = []string{
+ fmt.Sprint(step.contentLength),
+ }
+ }
+ st.writeHeaders(header)
+ case receiveDataHeader:
+ t.Logf("receive DATA frame header: size=%v", step.size)
+ st.writeVarint(int64(frameTypeData))
+ st.writeVarint(step.size)
+ st.Flush()
+ case receiveData:
+ t.Logf("receive DATA frame content: size=%v", step.size)
+ for range step.size {
+ st.stream.stream.WriteByte(byte(bytesSent))
+ bytesSent++
+ }
+ st.Flush()
+ case receiveTrailers:
+ st.writeHeaders(http.Header{
+ "x-trailer": []string{"trailer"},
+ })
+ case receiveFrame:
+ st.writeVarint(int64(step.ftype))
+ st.writeVarint(int64(len(step.data)))
+ st.Write(step.data)
+ st.Flush()
+ case receiveEOF:
+ t.Logf("receive EOF on request stream")
+ st.stream.stream.CloseWrite()
+ case wantBody:
+ t.Logf("read %v bytes from response body", step.size)
+ want := make([]byte, step.size)
+ for i := range want {
+ want[i] = byte(bytesReceived)
+ bytesReceived++
+ }
+ got := make([]byte, step.size)
+ n, err := body().Read(got)
+ got = got[:n]
+ if !bytes.Equal(got, want) {
+ t.Errorf("resp.Body.Read:")
+ t.Errorf(" got: {%x}", got)
+ t.Fatalf(" want: {%x}", want)
+ }
+ if err != nil {
+ if step.eof && err == io.EOF {
+ continue
+ }
+ t.Fatalf("resp.Body.Read: unexpected error %v", err)
+ }
+ if step.eof {
+ if n, err := body().Read([]byte{0}); n != 0 || err != io.EOF {
+ t.Fatalf("resp.Body.Read() = %v, %v; want io.EOF", n, err)
+ }
+ }
+ case wantError:
+ if n, err := body().Read([]byte{0}); n != 0 || err == nil || err == io.EOF {
+ t.Fatalf("resp.Body.Read() = %v, %v; want error", n, err)
+ }
+ default:
+ t.Fatalf("unknown test step %T", step)
+ }
+ }
+
+ }
+
+ runSynctestSubtest(t, test.name+"/client", func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+
+ header := http.Header{
+ ":status": []string{"200"},
+ }
+ runTest(t, header, st, func() io.ReadCloser {
+ return rt.response().Body
+ })
+ })
+ }
+}
diff --git a/internal/http3/conn.go b/internal/http3/conn.go
new file mode 100644
index 0000000000..5eb803115e
--- /dev/null
+++ b/internal/http3/conn.go
@@ -0,0 +1,133 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "context"
+ "io"
+ "sync"
+
+ "golang.org/x/net/quic"
+)
+
+type streamHandler interface {
+ handleControlStream(*stream) error
+ handlePushStream(*stream) error
+ handleEncoderStream(*stream) error
+ handleDecoderStream(*stream) error
+ handleRequestStream(*stream) error
+ abort(error)
+}
+
+type genericConn struct {
+ mu sync.Mutex
+
+ // The peer may create exactly one control, encoder, and decoder stream.
+ // streamsCreated is a bitset of streams created so far.
+ // Bits are 1 << streamType.
+ streamsCreated uint8
+}
+
+func (c *genericConn) acceptStreams(qconn *quic.Conn, h streamHandler) {
+ for {
+ // Use context.Background: This blocks until a stream is accepted
+ // or the connection closes.
+ st, err := qconn.AcceptStream(context.Background())
+ if err != nil {
+ return // connection closed
+ }
+ if st.IsReadOnly() {
+ go c.handleUnidirectionalStream(newStream(st), h)
+ } else {
+ go c.handleRequestStream(newStream(st), h)
+ }
+ }
+}
+
+func (c *genericConn) handleUnidirectionalStream(st *stream, h streamHandler) {
+ // Unidirectional stream header: One varint with the stream type.
+ v, err := st.readVarint()
+ if err != nil {
+ h.abort(&connectionError{
+ code: errH3StreamCreationError,
+ message: "error reading unidirectional stream header",
+ })
+ return
+ }
+ stype := streamType(v)
+ if err := c.checkStreamCreation(stype); err != nil {
+ h.abort(err)
+ return
+ }
+ switch stype {
+ case streamTypeControl:
+ err = h.handleControlStream(st)
+ case streamTypePush:
+ err = h.handlePushStream(st)
+ case streamTypeEncoder:
+ err = h.handleEncoderStream(st)
+ case streamTypeDecoder:
+ err = h.handleDecoderStream(st)
+ default:
+ // "Recipients of unknown stream types MUST either abort reading
+ // of the stream or discard incoming data without further processing."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2-7
+ //
+ // We should send the H3_STREAM_CREATION_ERROR error code,
+ // but the quic package currently doesn't allow setting error codes
+ // for STOP_SENDING frames.
+ // TODO: Should CloseRead take an error code?
+ err = nil
+ }
+ if err == io.EOF {
+ err = &connectionError{
+ code: errH3ClosedCriticalStream,
+ message: streamType(stype).String() + " stream closed",
+ }
+ }
+ c.handleStreamError(st, h, err)
+}
+
+func (c *genericConn) handleRequestStream(st *stream, h streamHandler) {
+ c.handleStreamError(st, h, h.handleRequestStream(st))
+}
+
+func (c *genericConn) handleStreamError(st *stream, h streamHandler, err error) {
+ switch err := err.(type) {
+ case *connectionError:
+ h.abort(err)
+ case nil:
+ st.stream.CloseRead()
+ st.stream.CloseWrite()
+ case *streamError:
+ st.stream.CloseRead()
+ st.stream.Reset(uint64(err.code))
+ default:
+ st.stream.CloseRead()
+ st.stream.Reset(uint64(errH3InternalError))
+ }
+}
+
+func (c *genericConn) checkStreamCreation(stype streamType) error {
+ switch stype {
+ case streamTypeControl, streamTypeEncoder, streamTypeDecoder:
+ // The peer may create exactly one control, encoder, and decoder stream.
+ default:
+ return nil
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ bit := uint8(1) << stype
+ if c.streamsCreated&bit != 0 {
+ return &connectionError{
+ code: errH3StreamCreationError,
+ message: "multiple " + stype.String() + " streams created",
+ }
+ }
+ c.streamsCreated |= bit
+ return nil
+}
diff --git a/internal/http3/conn_test.go b/internal/http3/conn_test.go
new file mode 100644
index 0000000000..a9afb1f9e9
--- /dev/null
+++ b/internal/http3/conn_test.go
@@ -0,0 +1,154 @@
+// 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.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "testing"
+ "testing/synctest"
+)
+
+// Tests which apply to both client and server connections.
+
+func TestConnCreatesControlStream(t *testing.T) {
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ controlStream := tc.wantStream(streamTypeControl)
+ controlStream.wantFrameHeader(
+ "server sends SETTINGS frame on control stream",
+ frameTypeSettings)
+ controlStream.discardFrame()
+ })
+}
+
+func TestConnUnknownUnidirectionalStream(t *testing.T) {
+ // "Recipients of unknown stream types MUST either abort reading of the stream
+ // or discard incoming data without further processing."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2-7
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ st := tc.newStream(0x21) // reserved stream type
+
+ // The endpoint should send a STOP_SENDING for this stream,
+ // but it should not close the connection.
+ synctest.Wait()
+ if _, err := st.Write([]byte("hello")); err == nil {
+ t.Fatalf("write to send-only stream with an unknown type succeeded; want error")
+ }
+ tc.wantNotClosed("after receiving unknown unidirectional stream type")
+ })
+}
+
+func TestConnUnknownSettings(t *testing.T) {
+ // "An implementation MUST ignore any [settings] parameter with
+ // an identifier it does not understand."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-9
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ controlStream := tc.newStream(streamTypeControl)
+ controlStream.writeSettings(0x1f+0x21, 0) // reserved settings type
+ controlStream.Flush()
+ tc.wantNotClosed("after receiving unknown settings")
+ })
+}
+
+func TestConnInvalidSettings(t *testing.T) {
+ // "These reserved settings MUST NOT be sent, and their receipt MUST
+ // be treated as a connection error of type H3_SETTINGS_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-5
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ controlStream := tc.newStream(streamTypeControl)
+ controlStream.writeSettings(0x02, 0) // HTTP/2 SETTINGS_ENABLE_PUSH
+ controlStream.Flush()
+ tc.wantClosed("invalid setting", errH3SettingsError)
+ })
+}
+
+func TestConnDuplicateStream(t *testing.T) {
+ for _, stype := range []streamType{
+ streamTypeControl,
+ streamTypeEncoder,
+ streamTypeDecoder,
+ } {
+ t.Run(stype.String(), func(t *testing.T) {
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ _ = tc.newStream(stype)
+ tc.wantNotClosed("after creating one " + stype.String() + " stream")
+
+ // Opening a second control, encoder, or decoder stream
+ // is a protocol violation.
+ _ = tc.newStream(stype)
+ tc.wantClosed("duplicate stream", errH3StreamCreationError)
+ })
+ })
+ }
+}
+
+func TestConnUnknownFrames(t *testing.T) {
+ for _, stype := range []streamType{
+ streamTypeControl,
+ } {
+ t.Run(stype.String(), func(t *testing.T) {
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ st := tc.newStream(stype)
+
+ if stype == streamTypeControl {
+ // First frame on the control stream must be settings.
+ st.writeVarint(int64(frameTypeSettings))
+ st.writeVarint(0) // size
+ }
+
+ data := "frame content"
+ st.writeVarint(0x1f + 0x21) // reserved frame type
+ st.writeVarint(int64(len(data))) // size
+ st.Write([]byte(data))
+ st.Flush()
+
+ tc.wantNotClosed("after writing unknown frame")
+ })
+ })
+ }
+}
+
+func TestConnInvalidFrames(t *testing.T) {
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ control := tc.newStream(streamTypeControl)
+
+ // SETTINGS frame.
+ control.writeVarint(int64(frameTypeSettings))
+ control.writeVarint(0) // size
+
+ // DATA frame (invalid on the control stream).
+ control.writeVarint(int64(frameTypeData))
+ control.writeVarint(0) // size
+ control.Flush()
+ tc.wantClosed("after writing DATA frame to control stream", errH3FrameUnexpected)
+ })
+}
+
+func TestConnPeerCreatesBadUnidirectionalStream(t *testing.T) {
+ runConnTest(t, func(t testing.TB, tc *testQUICConn) {
+ // Create and close a stream without sending the unidirectional stream header.
+ qs, err := tc.qconn.NewSendOnlyStream(canceledCtx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ st := newTestQUICStream(tc.t, newStream(qs))
+ st.stream.stream.Close()
+
+ tc.wantClosed("after peer creates and closes uni stream", errH3StreamCreationError)
+ })
+}
+
+func runConnTest(t *testing.T, f func(testing.TB, *testQUICConn)) {
+ t.Helper()
+ runSynctestSubtest(t, "client", func(t testing.TB) {
+ tc := newTestClientConn(t)
+ f(t, tc.testQUICConn)
+ })
+ runSynctestSubtest(t, "server", func(t testing.TB) {
+ ts := newTestServer(t)
+ tc := ts.connect()
+ f(t, tc.testQUICConn)
+ })
+}
diff --git a/internal/http3/doc.go b/internal/http3/doc.go
new file mode 100644
index 0000000000..5530113f69
--- /dev/null
+++ b/internal/http3/doc.go
@@ -0,0 +1,10 @@
+// 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 http3 implements the HTTP/3 protocol.
+//
+// This package is a work in progress.
+// It is not ready for production usage.
+// Its API is subject to change without notice.
+package http3
diff --git a/internal/http3/errors.go b/internal/http3/errors.go
new file mode 100644
index 0000000000..db46acfcc8
--- /dev/null
+++ b/internal/http3/errors.go
@@ -0,0 +1,104 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import "fmt"
+
+// http3Error is an HTTP/3 error code.
+type http3Error int
+
+const (
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-8.1
+ errH3NoError = http3Error(0x0100)
+ errH3GeneralProtocolError = http3Error(0x0101)
+ errH3InternalError = http3Error(0x0102)
+ errH3StreamCreationError = http3Error(0x0103)
+ errH3ClosedCriticalStream = http3Error(0x0104)
+ errH3FrameUnexpected = http3Error(0x0105)
+ errH3FrameError = http3Error(0x0106)
+ errH3ExcessiveLoad = http3Error(0x0107)
+ errH3IDError = http3Error(0x0108)
+ errH3SettingsError = http3Error(0x0109)
+ errH3MissingSettings = http3Error(0x010a)
+ errH3RequestRejected = http3Error(0x010b)
+ errH3RequestCancelled = http3Error(0x010c)
+ errH3RequestIncomplete = http3Error(0x010d)
+ errH3MessageError = http3Error(0x010e)
+ errH3ConnectError = http3Error(0x010f)
+ errH3VersionFallback = http3Error(0x0110)
+
+ // https://www.rfc-editor.org/rfc/rfc9204.html#section-8.3
+ errQPACKDecompressionFailed = http3Error(0x0200)
+ errQPACKEncoderStreamError = http3Error(0x0201)
+ errQPACKDecoderStreamError = http3Error(0x0202)
+)
+
+func (e http3Error) Error() string {
+ switch e {
+ case errH3NoError:
+ return "H3_NO_ERROR"
+ case errH3GeneralProtocolError:
+ return "H3_GENERAL_PROTOCOL_ERROR"
+ case errH3InternalError:
+ return "H3_INTERNAL_ERROR"
+ case errH3StreamCreationError:
+ return "H3_STREAM_CREATION_ERROR"
+ case errH3ClosedCriticalStream:
+ return "H3_CLOSED_CRITICAL_STREAM"
+ case errH3FrameUnexpected:
+ return "H3_FRAME_UNEXPECTED"
+ case errH3FrameError:
+ return "H3_FRAME_ERROR"
+ case errH3ExcessiveLoad:
+ return "H3_EXCESSIVE_LOAD"
+ case errH3IDError:
+ return "H3_ID_ERROR"
+ case errH3SettingsError:
+ return "H3_SETTINGS_ERROR"
+ case errH3MissingSettings:
+ return "H3_MISSING_SETTINGS"
+ case errH3RequestRejected:
+ return "H3_REQUEST_REJECTED"
+ case errH3RequestCancelled:
+ return "H3_REQUEST_CANCELLED"
+ case errH3RequestIncomplete:
+ return "H3_REQUEST_INCOMPLETE"
+ case errH3MessageError:
+ return "H3_MESSAGE_ERROR"
+ case errH3ConnectError:
+ return "H3_CONNECT_ERROR"
+ case errH3VersionFallback:
+ return "H3_VERSION_FALLBACK"
+ case errQPACKDecompressionFailed:
+ return "QPACK_DECOMPRESSION_FAILED"
+ case errQPACKEncoderStreamError:
+ return "QPACK_ENCODER_STREAM_ERROR"
+ case errQPACKDecoderStreamError:
+ return "QPACK_DECODER_STREAM_ERROR"
+ }
+ return fmt.Sprintf("H3_ERROR_%v", int(e))
+}
+
+// A streamError is an error which terminates a stream, but not the connection.
+// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-1
+type streamError struct {
+ code http3Error
+ message string
+}
+
+func (e *streamError) Error() string { return e.message }
+func (e *streamError) Unwrap() error { return e.code }
+
+// A connectionError is an error which results in the entire connection closing.
+// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-2
+type connectionError struct {
+ code http3Error
+ message string
+}
+
+func (e *connectionError) Error() string { return e.message }
+func (e *connectionError) Unwrap() error { return e.code }
diff --git a/internal/http3/files_test.go b/internal/http3/files_test.go
new file mode 100644
index 0000000000..9c97a6ced4
--- /dev/null
+++ b/internal/http3/files_test.go
@@ -0,0 +1,56 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "bytes"
+ "os"
+ "strings"
+ "testing"
+)
+
+// TestFiles checks that every file in this package has a build constraint on Go 1.24.
+//
+// Package tests rely on testing/synctest, added as an experiment in Go 1.24.
+// When moving internal/http3 to an importable location, we can decide whether
+// to relax the constraint for non-test files.
+//
+// Drop this test when the x/net go.mod depends on 1.24 or newer.
+func TestFiles(t *testing.T) {
+ f, err := os.Open(".")
+ if err != nil {
+ t.Fatal(err)
+ }
+ names, err := f.Readdirnames(-1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, name := range names {
+ if !strings.HasSuffix(name, ".go") {
+ continue
+ }
+ b, err := os.ReadFile(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Check for copyright header while we're in here.
+ if !bytes.Contains(b, []byte("The Go Authors.")) {
+ t.Errorf("%v: missing copyright", name)
+ }
+ // doc.go doesn't need a build constraint.
+ if name == "doc.go" {
+ continue
+ }
+ if !bytes.Contains(b, []byte("//go:build go1.24")) {
+ t.Errorf("%v: missing constraint on go1.24", name)
+ }
+ if bytes.Contains(b, []byte(`"testing/synctest"`)) &&
+ !bytes.Contains(b, []byte("//go:build go1.24 && goexperiment.synctest")) {
+ t.Errorf("%v: missing constraint on go1.24 && goexperiment.synctest", name)
+ }
+ }
+}
diff --git a/internal/http3/http3.go b/internal/http3/http3.go
new file mode 100644
index 0000000000..1f60670564
--- /dev/null
+++ b/internal/http3/http3.go
@@ -0,0 +1,86 @@
+// 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.
+
+//go:build go1.24
+
+package http3
+
+import "fmt"
+
+// Stream types.
+//
+// For unidirectional streams, the value is the stream type sent over the wire.
+//
+// For bidirectional streams (which are always request streams),
+// the value is arbitrary and never sent on the wire.
+type streamType int64
+
+const (
+ // Bidirectional request stream.
+ // All bidirectional streams are request streams.
+ // This stream type is never sent over the wire.
+ //
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1
+ streamTypeRequest = streamType(-1)
+
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2
+ streamTypeControl = streamType(0x00)
+ streamTypePush = streamType(0x01)
+
+ // https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2
+ streamTypeEncoder = streamType(0x02)
+ streamTypeDecoder = streamType(0x03)
+)
+
+func (stype streamType) String() string {
+ switch stype {
+ case streamTypeRequest:
+ return "request"
+ case streamTypeControl:
+ return "control"
+ case streamTypePush:
+ return "push"
+ case streamTypeEncoder:
+ return "encoder"
+ case streamTypeDecoder:
+ return "decoder"
+ default:
+ return "unknown"
+ }
+}
+
+// Frame types.
+type frameType int64
+
+const (
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2
+ frameTypeData = frameType(0x00)
+ frameTypeHeaders = frameType(0x01)
+ frameTypeCancelPush = frameType(0x03)
+ frameTypeSettings = frameType(0x04)
+ frameTypePushPromise = frameType(0x05)
+ frameTypeGoaway = frameType(0x07)
+ frameTypeMaxPushID = frameType(0x0d)
+)
+
+func (ftype frameType) String() string {
+ switch ftype {
+ case frameTypeData:
+ return "DATA"
+ case frameTypeHeaders:
+ return "HEADERS"
+ case frameTypeCancelPush:
+ return "CANCEL_PUSH"
+ case frameTypeSettings:
+ return "SETTINGS"
+ case frameTypePushPromise:
+ return "PUSH_PROMISE"
+ case frameTypeGoaway:
+ return "GOAWAY"
+ case frameTypeMaxPushID:
+ return "MAX_PUSH_ID"
+ default:
+ return fmt.Sprintf("UNKNOWN_%d", int64(ftype))
+ }
+}
diff --git a/internal/http3/http3_synctest_test.go b/internal/http3/http3_synctest_test.go
new file mode 100644
index 0000000000..ad26c6de09
--- /dev/null
+++ b/internal/http3/http3_synctest_test.go
@@ -0,0 +1,48 @@
+// 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.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "slices"
+ "testing"
+ "testing/synctest"
+)
+
+// runSynctest runs f in a synctest.Run bubble.
+// It arranges for t.Cleanup functions to run within the bubble.
+func runSynctest(t *testing.T, f func(t testing.TB)) {
+ synctest.Run(func() {
+ ct := &cleanupT{T: t}
+ defer ct.done()
+ f(ct)
+ })
+}
+
+// runSynctestSubtest runs f in a subtest in a synctest.Run bubble.
+func runSynctestSubtest(t *testing.T, name string, f func(t testing.TB)) {
+ t.Run(name, func(t *testing.T) {
+ runSynctest(t, f)
+ })
+}
+
+// cleanupT wraps a testing.T and adds its own Cleanup method.
+// Used to execute cleanup functions within a synctest bubble.
+type cleanupT struct {
+ *testing.T
+ cleanups []func()
+}
+
+// Cleanup replaces T.Cleanup.
+func (t *cleanupT) Cleanup(f func()) {
+ t.cleanups = append(t.cleanups, f)
+}
+
+func (t *cleanupT) done() {
+ for _, f := range slices.Backward(t.cleanups) {
+ f()
+ }
+}
diff --git a/internal/http3/http3_test.go b/internal/http3/http3_test.go
new file mode 100644
index 0000000000..f6fb2e9b34
--- /dev/null
+++ b/internal/http3/http3_test.go
@@ -0,0 +1,44 @@
+// 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.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "encoding/hex"
+ "os"
+ "strings"
+)
+
+func init() {
+ // testing/synctest requires asynctimerchan=0 (the default as of Go 1.23),
+ // but the x/net go.mod is currently selecting go1.18.
+ //
+ // Set asynctimerchan=0 explicitly.
+ //
+ // TODO: Remove this when the x/net go.mod Go version is >= go1.23.
+ os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",asynctimerchan=0")
+}
+
+func unhex(s string) []byte {
+ b, err := hex.DecodeString(strings.Map(func(c rune) rune {
+ switch c {
+ case ' ', '\t', '\n':
+ return -1 // ignore
+ }
+ return c
+ }, s))
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+// testReader implements io.Reader.
+type testReader struct {
+ readFunc func([]byte) (int, error)
+}
+
+func (r testReader) Read(p []byte) (n int, err error) { return r.readFunc(p) }
diff --git a/internal/http3/qpack.go b/internal/http3/qpack.go
new file mode 100644
index 0000000000..66f4e29762
--- /dev/null
+++ b/internal/http3/qpack.go
@@ -0,0 +1,334 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "errors"
+ "io"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+// QPACK (RFC 9204) header compression wire encoding.
+// https://www.rfc-editor.org/rfc/rfc9204.html
+
+// tableType is the static or dynamic table.
+//
+// The T bit in QPACK instructions indicates whether a table index refers to
+// the dynamic (T=0) or static (T=1) table. tableTypeForTBit and tableType.tbit
+// convert a T bit from the wire encoding to/from a tableType.
+type tableType byte
+
+const (
+ dynamicTable = 0x00 // T=0, dynamic table
+ staticTable = 0xff // T=1, static table
+)
+
+// tableTypeForTbit returns the table type corresponding to a T bit value.
+// The input parameter contains a byte masked to contain only the T bit.
+func tableTypeForTbit(bit byte) tableType {
+ if bit == 0 {
+ return dynamicTable
+ }
+ return staticTable
+}
+
+// tbit produces the T bit corresponding to the table type.
+// The input parameter contains a byte with the T bit set to 1,
+// and the return is either the input or 0 depending on the table type.
+func (t tableType) tbit(bit byte) byte {
+ return bit & byte(t)
+}
+
+// indexType indicates a literal's indexing status.
+//
+// The N bit in QPACK instructions indicates whether a literal is "never-indexed".
+// A never-indexed literal (N=1) must not be encoded as an indexed literal if it
+// forwarded on another connection.
+//
+// (See https://www.rfc-editor.org/rfc/rfc9204.html#section-7.1 for details on the
+// security reasons for never-indexed literals.)
+type indexType byte
+
+const (
+ mayIndex = 0x00 // N=0, not a never-indexed literal
+ neverIndex = 0xff // N=1, never-indexed literal
+)
+
+// indexTypeForNBit returns the index type corresponding to a N bit value.
+// The input parameter contains a byte masked to contain only the N bit.
+func indexTypeForNBit(bit byte) indexType {
+ if bit == 0 {
+ return mayIndex
+ }
+ return neverIndex
+}
+
+// nbit produces the N bit corresponding to the table type.
+// The input parameter contains a byte with the N bit set to 1,
+// and the return is either the input or 0 depending on the table type.
+func (t indexType) nbit(bit byte) byte {
+ return bit & byte(t)
+}
+
+// Indexed Field Line:
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | 1 | T | Index (6+) |
+// +---+---+-----------------------+
+//
+// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.2
+
+func appendIndexedFieldLine(b []byte, ttype tableType, index int) []byte {
+ const tbit = 0b_01000000
+ return appendPrefixedInt(b, 0b_1000_0000|ttype.tbit(tbit), 6, int64(index))
+}
+
+func (st *stream) decodeIndexedFieldLine(b byte) (itype indexType, name, value string, err error) {
+ index, err := st.readPrefixedIntWithByte(b, 6)
+ if err != nil {
+ return 0, "", "", err
+ }
+ const tbit = 0b_0100_0000
+ if tableTypeForTbit(b&tbit) == staticTable {
+ ent, err := staticTableEntry(index)
+ if err != nil {
+ return 0, "", "", err
+ }
+ return mayIndex, ent.name, ent.value, nil
+ } else {
+ return 0, "", "", errors.New("dynamic table is not supported yet")
+ }
+}
+
+// Literal Field Line With Name Reference:
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | 0 | 1 | N | T |Name Index (4+)|
+// +---+---+---+---+---------------+
+// | H | Value Length (7+) |
+// +---+---------------------------+
+// | Value String (Length bytes) |
+// +-------------------------------+
+//
+// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.4
+
+func appendLiteralFieldLineWithNameReference(b []byte, ttype tableType, itype indexType, nameIndex int, value string) []byte {
+ const tbit = 0b_0001_0000
+ const nbit = 0b_0010_0000
+ b = appendPrefixedInt(b, 0b_0100_0000|itype.nbit(nbit)|ttype.tbit(tbit), 4, int64(nameIndex))
+ b = appendPrefixedString(b, 0, 7, value)
+ return b
+}
+
+func (st *stream) decodeLiteralFieldLineWithNameReference(b byte) (itype indexType, name, value string, err error) {
+ nameIndex, err := st.readPrefixedIntWithByte(b, 4)
+ if err != nil {
+ return 0, "", "", err
+ }
+
+ const tbit = 0b_0001_0000
+ if tableTypeForTbit(b&tbit) == staticTable {
+ ent, err := staticTableEntry(nameIndex)
+ if err != nil {
+ return 0, "", "", err
+ }
+ name = ent.name
+ } else {
+ return 0, "", "", errors.New("dynamic table is not supported yet")
+ }
+
+ _, value, err = st.readPrefixedString(7)
+ if err != nil {
+ return 0, "", "", err
+ }
+
+ const nbit = 0b_0010_0000
+ itype = indexTypeForNBit(b & nbit)
+
+ return itype, name, value, nil
+}
+
+// Literal Field Line with Literal Name:
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | 0 | 0 | 1 | N | H |NameLen(3+)|
+// +---+---+---+---+---+-----------+
+// | Name String (Length bytes) |
+// +---+---------------------------+
+// | H | Value Length (7+) |
+// +---+---------------------------+
+// | Value String (Length bytes) |
+// +-------------------------------+
+//
+// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.6
+
+func appendLiteralFieldLineWithLiteralName(b []byte, itype indexType, name, value string) []byte {
+ const nbit = 0b_0001_0000
+ b = appendPrefixedString(b, 0b_0010_0000|itype.nbit(nbit), 3, name)
+ b = appendPrefixedString(b, 0, 7, value)
+ return b
+}
+
+func (st *stream) decodeLiteralFieldLineWithLiteralName(b byte) (itype indexType, name, value string, err error) {
+ name, err = st.readPrefixedStringWithByte(b, 3)
+ if err != nil {
+ return 0, "", "", err
+ }
+ _, value, err = st.readPrefixedString(7)
+ if err != nil {
+ return 0, "", "", err
+ }
+ const nbit = 0b_0001_0000
+ itype = indexTypeForNBit(b & nbit)
+ return itype, name, value, nil
+}
+
+// Prefixed-integer encoding from RFC 7541, section 5.1
+//
+// Prefixed integers consist of some number of bits of data,
+// N bits of encoded integer, and 0 or more additional bytes of
+// encoded integer.
+//
+// The RFCs represent this as, for example:
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | 0 | 0 | 1 | Capacity (5+) |
+// +---+---+---+-------------------+
+//
+// "Capacity" is an integer with a 5-bit prefix.
+//
+// In the following functions, a "prefixLen" parameter is the number
+// of integer bits in the first byte (5 in the above example), and
+// a "firstByte" parameter is a byte containing the first byte of
+// the encoded value (0x001x_xxxx in the above example).
+//
+// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.1
+// https://www.rfc-editor.org/rfc/rfc7541#section-5.1
+
+// readPrefixedInt reads an RFC 7541 prefixed integer from st.
+func (st *stream) readPrefixedInt(prefixLen uint8) (firstByte byte, v int64, err error) {
+ firstByte, err = st.ReadByte()
+ if err != nil {
+ return 0, 0, errQPACKDecompressionFailed
+ }
+ v, err = st.readPrefixedIntWithByte(firstByte, prefixLen)
+ return firstByte, v, err
+}
+
+// readPrefixedInt reads an RFC 7541 prefixed integer from st.
+// The first byte has already been read from the stream.
+func (st *stream) readPrefixedIntWithByte(firstByte byte, prefixLen uint8) (v int64, err error) {
+ prefixMask := (byte(1) << prefixLen) - 1
+ v = int64(firstByte & prefixMask)
+ if v != int64(prefixMask) {
+ return v, nil
+ }
+ m := 0
+ for {
+ b, err := st.ReadByte()
+ if err != nil {
+ return 0, errQPACKDecompressionFailed
+ }
+ v += int64(b&127) << m
+ m += 7
+ if b&128 == 0 {
+ break
+ }
+ }
+ return v, err
+}
+
+// appendPrefixedInt appends an RFC 7541 prefixed integer to b.
+//
+// The firstByte parameter includes the non-integer bits of the first byte.
+// The other bits must be zero.
+func appendPrefixedInt(b []byte, firstByte byte, prefixLen uint8, i int64) []byte {
+ u := uint64(i)
+ prefixMask := (uint64(1) << prefixLen) - 1
+ if u < prefixMask {
+ return append(b, firstByte|byte(u))
+ }
+ b = append(b, firstByte|byte(prefixMask))
+ u -= prefixMask
+ for u >= 128 {
+ b = append(b, 0x80|byte(u&0x7f))
+ u >>= 7
+ }
+ return append(b, byte(u))
+}
+
+// String literal encoding from RFC 7541, section 5.2
+//
+// String literals consist of a single bit flag indicating
+// whether the string is Huffman-encoded, a prefixed integer (see above),
+// and the string.
+//
+// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2
+// https://www.rfc-editor.org/rfc/rfc7541#section-5.2
+
+// readPrefixedString reads an RFC 7541 string from st.
+func (st *stream) readPrefixedString(prefixLen uint8) (firstByte byte, s string, err error) {
+ firstByte, err = st.ReadByte()
+ if err != nil {
+ return 0, "", errQPACKDecompressionFailed
+ }
+ s, err = st.readPrefixedStringWithByte(firstByte, prefixLen)
+ return firstByte, s, err
+}
+
+// readPrefixedString reads an RFC 7541 string from st.
+// The first byte has already been read from the stream.
+func (st *stream) readPrefixedStringWithByte(firstByte byte, prefixLen uint8) (s string, err error) {
+ size, err := st.readPrefixedIntWithByte(firstByte, prefixLen)
+ if err != nil {
+ return "", errQPACKDecompressionFailed
+ }
+
+ hbit := byte(1) << prefixLen
+ isHuffman := firstByte&hbit != 0
+
+ // TODO: Avoid allocating here.
+ data := make([]byte, size)
+ if _, err := io.ReadFull(st, data); err != nil {
+ return "", errQPACKDecompressionFailed
+ }
+ if isHuffman {
+ // TODO: Move Huffman functions into a new package that hpack (HTTP/2)
+ // and this package can both import. Most of the hpack package isn't
+ // relevant to HTTP/3.
+ s, err := hpack.HuffmanDecodeToString(data)
+ if err != nil {
+ return "", errQPACKDecompressionFailed
+ }
+ return s, nil
+ }
+ return string(data), nil
+}
+
+// appendPrefixedString appends an RFC 7541 string to st,
+// applying Huffman encoding and setting the H bit (indicating Huffman encoding)
+// when appropriate.
+//
+// The firstByte parameter includes the non-integer bits of the first byte.
+// The other bits must be zero.
+func appendPrefixedString(b []byte, firstByte byte, prefixLen uint8, s string) []byte {
+ huffmanLen := hpack.HuffmanEncodeLength(s)
+ if huffmanLen < uint64(len(s)) {
+ hbit := byte(1) << prefixLen
+ b = appendPrefixedInt(b, firstByte|hbit, prefixLen, int64(huffmanLen))
+ b = hpack.AppendHuffmanString(b, s)
+ } else {
+ b = appendPrefixedInt(b, firstByte, prefixLen, int64(len(s)))
+ b = append(b, s...)
+ }
+ return b
+}
diff --git a/internal/http3/qpack_decode.go b/internal/http3/qpack_decode.go
new file mode 100644
index 0000000000..018867afb1
--- /dev/null
+++ b/internal/http3/qpack_decode.go
@@ -0,0 +1,83 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "errors"
+ "math/bits"
+)
+
+type qpackDecoder struct {
+ // The decoder has no state for now,
+ // but that'll change once we add dynamic table support.
+ //
+ // TODO: dynamic table support.
+}
+
+func (qd *qpackDecoder) decode(st *stream, f func(itype indexType, name, value string) error) error {
+ // Encoded Field Section prefix.
+
+ // We set SETTINGS_QPACK_MAX_TABLE_CAPACITY to 0,
+ // so the Required Insert Count must be 0.
+ _, requiredInsertCount, err := st.readPrefixedInt(8)
+ if err != nil {
+ return err
+ }
+ if requiredInsertCount != 0 {
+ return errQPACKDecompressionFailed
+ }
+
+ // Delta Base. We don't use the dynamic table yet, so this may be ignored.
+ _, _, err = st.readPrefixedInt(7)
+ if err != nil {
+ return err
+ }
+
+ sawNonPseudo := false
+ for st.lim > 0 {
+ firstByte, err := st.ReadByte()
+ if err != nil {
+ return err
+ }
+ var name, value string
+ var itype indexType
+ switch bits.LeadingZeros8(firstByte) {
+ case 0:
+ // Indexed Field Line
+ itype, name, value, err = st.decodeIndexedFieldLine(firstByte)
+ case 1:
+ // Literal Field Line With Name Reference
+ itype, name, value, err = st.decodeLiteralFieldLineWithNameReference(firstByte)
+ case 2:
+ // Literal Field Line with Literal Name
+ itype, name, value, err = st.decodeLiteralFieldLineWithLiteralName(firstByte)
+ case 3:
+ // Indexed Field Line With Post-Base Index
+ err = errors.New("dynamic table is not supported yet")
+ case 4:
+ // Indexed Field Line With Post-Base Name Reference
+ err = errors.New("dynamic table is not supported yet")
+ }
+ if err != nil {
+ return err
+ }
+ if len(name) == 0 {
+ return errH3MessageError
+ }
+ if name[0] == ':' {
+ if sawNonPseudo {
+ return errH3MessageError
+ }
+ } else {
+ sawNonPseudo = true
+ }
+ if err := f(itype, name, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/internal/http3/qpack_decode_test.go b/internal/http3/qpack_decode_test.go
new file mode 100644
index 0000000000..3b9a995fa0
--- /dev/null
+++ b/internal/http3/qpack_decode_test.go
@@ -0,0 +1,196 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestQPACKDecode(t *testing.T) {
+ type header struct {
+ itype indexType
+ name, value string
+ }
+ // Many test cases here taken from Google QUICHE,
+ // quiche/quic/core/qpack/qpack_encoder_test.cc.
+ for _, test := range []struct {
+ name string
+ enc []byte
+ want []header
+ }{{
+ name: "empty",
+ enc: unhex("0000"),
+ want: []header{},
+ }, {
+ name: "literal entry empty value",
+ enc: unhex("000023666f6f00"),
+ want: []header{
+ {mayIndex, "foo", ""},
+ },
+ }, {
+ name: "simple literal entry",
+ enc: unhex("000023666f6f03626172"),
+ want: []header{
+ {mayIndex, "foo", "bar"},
+ },
+ }, {
+ name: "multiple literal entries",
+ enc: unhex("0000" + // prefix
+ // foo: bar
+ "23666f6f03626172" +
+ // 7 octet long header name, the smallest number
+ // that does not fit on a 3-bit prefix.
+ "2700666f6f62616172" +
+ // 127 octet long header value, the smallest number
+ // that does not fit on a 7-bit prefix.
+ "7f00616161616161616161616161616161616161616161616161616161616161616161" +
+ "6161616161616161616161616161616161616161616161616161616161616161616161" +
+ "6161616161616161616161616161616161616161616161616161616161616161616161" +
+ "616161616161616161616161616161616161616161616161",
+ ),
+ want: []header{
+ {mayIndex, "foo", "bar"},
+ {mayIndex, "foobaar", strings.Repeat("a", 127)},
+ },
+ }, {
+ name: "line feed in value",
+ enc: unhex("000023666f6f0462610a72"),
+ want: []header{
+ {mayIndex, "foo", "ba\nr"},
+ },
+ }, {
+ name: "huffman simple",
+ enc: unhex("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf"),
+ want: []header{
+ {mayIndex, "custom-key", "custom-value"},
+ },
+ }, {
+ name: "alternating huffman nonhuffman",
+ enc: unhex("0000" + // Prefix.
+ "2f0125a849e95ba97d7f" + // Huffman-encoded name.
+ "8925a849e95bb8e8b4bf" + // Huffman-encoded value.
+ "2703637573746f6d2d6b6579" + // Non-Huffman encoded name.
+ "0c637573746f6d2d76616c7565" + // Non-Huffman encoded value.
+ "2f0125a849e95ba97d7f" + // Huffman-encoded name.
+ "0c637573746f6d2d76616c7565" + // Non-Huffman encoded value.
+ "2703637573746f6d2d6b6579" + // Non-Huffman encoded name.
+ "8925a849e95bb8e8b4bf", // Huffman-encoded value.
+ ),
+ want: []header{
+ {mayIndex, "custom-key", "custom-value"},
+ {mayIndex, "custom-key", "custom-value"},
+ {mayIndex, "custom-key", "custom-value"},
+ {mayIndex, "custom-key", "custom-value"},
+ },
+ }, {
+ name: "static table",
+ enc: unhex("0000d1d45f00055452414345dfcc5f108621e9aec2a11f5c8294e75f1000"),
+ want: []header{
+ {mayIndex, ":method", "GET"},
+ {mayIndex, ":method", "POST"},
+ {mayIndex, ":method", "TRACE"},
+ {mayIndex, "accept-encoding", "gzip, deflate, br"},
+ {mayIndex, "location", ""},
+ {mayIndex, "accept-encoding", "compress"},
+ {mayIndex, "location", "foo"},
+ {mayIndex, "accept-encoding", ""},
+ },
+ }} {
+ runSynctestSubtest(t, test.name, func(t testing.TB) {
+ st1, st2 := newStreamPair(t)
+ st1.Write(test.enc)
+ st1.Flush()
+
+ st2.lim = int64(len(test.enc))
+
+ var dec qpackDecoder
+ got := []header{}
+ err := dec.decode(st2, func(itype indexType, name, value string) error {
+ got = append(got, header{itype, name, value})
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("encoded: %x", test.enc)
+ t.Errorf("got headers:")
+ for _, h := range got {
+ t.Errorf(" %v: %q", h.name, h.value)
+ }
+ t.Errorf("want headers:")
+ for _, h := range test.want {
+ t.Errorf(" %v: %q", h.name, h.value)
+ }
+ }
+ })
+ }
+}
+
+func TestQPACKDecodeErrors(t *testing.T) {
+ // Many test cases here taken from Google QUICHE,
+ // quiche/quic/core/qpack/qpack_encoder_test.cc.
+ for _, test := range []struct {
+ name string
+ enc []byte
+ }{{
+ name: "literal entry empty name",
+ enc: unhex("00002003666f6f"),
+ }, {
+ name: "literal entry empty name and value",
+ enc: unhex("00002000"),
+ }, {
+ name: "name length too large for varint",
+ enc: unhex("000027ffffffffffffffffffff"),
+ }, {
+ name: "string literal too long",
+ enc: unhex("000027ffff7f"),
+ }, {
+ name: "value length too large for varint",
+ enc: unhex("000023666f6f7fffffffffffffffffffff"),
+ }, {
+ name: "value length too long",
+ enc: unhex("000023666f6f7fffff7f"),
+ }, {
+ name: "incomplete header block",
+ enc: unhex("00002366"),
+ }, {
+ name: "huffman name does not have eos prefix",
+ enc: unhex("00002f0125a849e95ba97d7e8925a849e95bb8e8b4bf"),
+ }, {
+ name: "huffman value does not have eos prefix",
+ enc: unhex("00002f0125a849e95ba97d7f8925a849e95bb8e8b4be"),
+ }, {
+ name: "huffman name eos prefix too long",
+ enc: unhex("00002f0225a849e95ba97d7fff8925a849e95bb8e8b4bf"),
+ }, {
+ name: "huffman value eos prefix too long",
+ enc: unhex("00002f0125a849e95ba97d7f8a25a849e95bb8e8b4bfff"),
+ }, {
+ name: "too high static table index",
+ enc: unhex("0000ff23ff24"),
+ }} {
+ runSynctestSubtest(t, test.name, func(t testing.TB) {
+ st1, st2 := newStreamPair(t)
+ st1.Write(test.enc)
+ st1.Flush()
+
+ st2.lim = int64(len(test.enc))
+
+ var dec qpackDecoder
+ err := dec.decode(st2, func(itype indexType, name, value string) error {
+ return nil
+ })
+ if err == nil {
+ t.Errorf("encoded: %x", test.enc)
+ t.Fatalf("decode succeeded; want error")
+ }
+ })
+ }
+}
diff --git a/internal/http3/qpack_encode.go b/internal/http3/qpack_encode.go
new file mode 100644
index 0000000000..0f35e0c54f
--- /dev/null
+++ b/internal/http3/qpack_encode.go
@@ -0,0 +1,47 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+type qpackEncoder struct {
+ // The encoder has no state for now,
+ // but that'll change once we add dynamic table support.
+ //
+ // TODO: dynamic table support.
+}
+
+func (qe *qpackEncoder) init() {
+ staticTableOnce.Do(initStaticTableMaps)
+}
+
+// encode encodes a list of headers into a QPACK encoded field section.
+//
+// The headers func must produce the same headers on repeated calls,
+// although the order may vary.
+func (qe *qpackEncoder) encode(headers func(func(itype indexType, name, value string))) []byte {
+ // Encoded Field Section prefix.
+ //
+ // We don't yet use the dynamic table, so both values here are zero.
+ var b []byte
+ b = appendPrefixedInt(b, 0, 8, 0) // Required Insert Count
+ b = appendPrefixedInt(b, 0, 7, 0) // Delta Base
+
+ headers(func(itype indexType, name, value string) {
+ if itype == mayIndex {
+ if i, ok := staticTableByNameValue[tableEntry{name, value}]; ok {
+ b = appendIndexedFieldLine(b, staticTable, i)
+ return
+ }
+ }
+ if i, ok := staticTableByName[name]; ok {
+ b = appendLiteralFieldLineWithNameReference(b, staticTable, itype, i, value)
+ } else {
+ b = appendLiteralFieldLineWithLiteralName(b, itype, name, value)
+ }
+ })
+
+ return b
+}
diff --git a/internal/http3/qpack_encode_test.go b/internal/http3/qpack_encode_test.go
new file mode 100644
index 0000000000..f426d773a6
--- /dev/null
+++ b/internal/http3/qpack_encode_test.go
@@ -0,0 +1,126 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func TestQPACKEncode(t *testing.T) {
+ type header struct {
+ itype indexType
+ name, value string
+ }
+ // Many test cases here taken from Google QUICHE,
+ // quiche/quic/core/qpack/qpack_encoder_test.cc.
+ for _, test := range []struct {
+ name string
+ headers []header
+ want []byte
+ }{{
+ name: "empty",
+ headers: []header{},
+ want: unhex("0000"),
+ }, {
+ name: "empty name",
+ headers: []header{
+ {mayIndex, "", "foo"},
+ },
+ want: unhex("0000208294e7"),
+ }, {
+ name: "empty value",
+ headers: []header{
+ {mayIndex, "foo", ""},
+ },
+ want: unhex("00002a94e700"),
+ }, {
+ name: "empty name and value",
+ headers: []header{
+ {mayIndex, "", ""},
+ },
+ want: unhex("00002000"),
+ }, {
+ name: "simple",
+ headers: []header{
+ {mayIndex, "foo", "bar"},
+ },
+ want: unhex("00002a94e703626172"),
+ }, {
+ name: "multiple",
+ headers: []header{
+ {mayIndex, "foo", "bar"},
+ {mayIndex, "ZZZZZZZ", strings.Repeat("Z", 127)},
+ },
+ want: unhex("0000" + // prefix
+ // foo: bar
+ "2a94e703626172" +
+ // 7 octet long header name, the smallest number
+ // that does not fit on a 3-bit prefix.
+ "27005a5a5a5a5a5a5a" +
+ // 127 octet long header value, the smallest
+ // number that does not fit on a 7-bit prefix.
+ "7f005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +
+ "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +
+ "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +
+ "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"),
+ }, {
+ name: "static table 1",
+ headers: []header{
+ {mayIndex, ":method", "GET"},
+ {mayIndex, "accept-encoding", "gzip, deflate, br"},
+ {mayIndex, "location", ""},
+ },
+ want: unhex("0000d1dfcc"),
+ }, {
+ name: "static table 2",
+ headers: []header{
+ {mayIndex, ":method", "POST"},
+ {mayIndex, "accept-encoding", "compress"},
+ {mayIndex, "location", "foo"},
+ },
+ want: unhex("0000d45f108621e9aec2a11f5c8294e7"),
+ }, {
+ name: "static table 3",
+ headers: []header{
+ {mayIndex, ":method", "TRACE"},
+ {mayIndex, "accept-encoding", ""},
+ },
+ want: unhex("00005f000554524143455f1000"),
+ }, {
+ name: "never indexed literal field line with name reference",
+ headers: []header{
+ {neverIndex, ":method", ""},
+ },
+ want: unhex("00007f0000"),
+ }, {
+ name: "never indexed literal field line with literal name",
+ headers: []header{
+ {neverIndex, "a", "b"},
+ },
+ want: unhex("000031610162"),
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ var enc qpackEncoder
+ enc.init()
+
+ got := enc.encode(func(f func(itype indexType, name, value string)) {
+ for _, h := range test.headers {
+ f(h.itype, h.name, h.value)
+ }
+ })
+ if !bytes.Equal(got, test.want) {
+ for _, h := range test.headers {
+ t.Logf("header %v: %q", h.name, h.value)
+ }
+ t.Errorf("got: %x", got)
+ t.Errorf("want: %x", test.want)
+ }
+ })
+ }
+}
diff --git a/internal/http3/qpack_static.go b/internal/http3/qpack_static.go
new file mode 100644
index 0000000000..cb0884eb7b
--- /dev/null
+++ b/internal/http3/qpack_static.go
@@ -0,0 +1,144 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import "sync"
+
+type tableEntry struct {
+ name string
+ value string
+}
+
+// staticTableEntry returns the static table entry with the given index.
+func staticTableEntry(index int64) (tableEntry, error) {
+ if index >= int64(len(staticTableEntries)) {
+ return tableEntry{}, errQPACKDecompressionFailed
+ }
+ return staticTableEntries[index], nil
+}
+
+func initStaticTableMaps() {
+ staticTableByName = make(map[string]int)
+ staticTableByNameValue = make(map[tableEntry]int)
+ for i, ent := range staticTableEntries {
+ if _, ok := staticTableByName[ent.name]; !ok {
+ staticTableByName[ent.name] = i
+ }
+ staticTableByNameValue[ent] = i
+ }
+}
+
+var (
+ staticTableOnce sync.Once
+ staticTableByName map[string]int
+ staticTableByNameValue map[tableEntry]int
+)
+
+// https://www.rfc-editor.org/rfc/rfc9204.html#appendix-A
+//
+// Note that this is different from the HTTP/2 static table.
+var staticTableEntries = [...]tableEntry{
+ 0: {":authority", ""},
+ 1: {":path", "/"},
+ 2: {"age", "0"},
+ 3: {"content-disposition", ""},
+ 4: {"content-length", "0"},
+ 5: {"cookie", ""},
+ 6: {"date", ""},
+ 7: {"etag", ""},
+ 8: {"if-modified-since", ""},
+ 9: {"if-none-match", ""},
+ 10: {"last-modified", ""},
+ 11: {"link", ""},
+ 12: {"location", ""},
+ 13: {"referer", ""},
+ 14: {"set-cookie", ""},
+ 15: {":method", "CONNECT"},
+ 16: {":method", "DELETE"},
+ 17: {":method", "GET"},
+ 18: {":method", "HEAD"},
+ 19: {":method", "OPTIONS"},
+ 20: {":method", "POST"},
+ 21: {":method", "PUT"},
+ 22: {":scheme", "http"},
+ 23: {":scheme", "https"},
+ 24: {":status", "103"},
+ 25: {":status", "200"},
+ 26: {":status", "304"},
+ 27: {":status", "404"},
+ 28: {":status", "503"},
+ 29: {"accept", "*/*"},
+ 30: {"accept", "application/dns-message"},
+ 31: {"accept-encoding", "gzip, deflate, br"},
+ 32: {"accept-ranges", "bytes"},
+ 33: {"access-control-allow-headers", "cache-control"},
+ 34: {"access-control-allow-headers", "content-type"},
+ 35: {"access-control-allow-origin", "*"},
+ 36: {"cache-control", "max-age=0"},
+ 37: {"cache-control", "max-age=2592000"},
+ 38: {"cache-control", "max-age=604800"},
+ 39: {"cache-control", "no-cache"},
+ 40: {"cache-control", "no-store"},
+ 41: {"cache-control", "public, max-age=31536000"},
+ 42: {"content-encoding", "br"},
+ 43: {"content-encoding", "gzip"},
+ 44: {"content-type", "application/dns-message"},
+ 45: {"content-type", "application/javascript"},
+ 46: {"content-type", "application/json"},
+ 47: {"content-type", "application/x-www-form-urlencoded"},
+ 48: {"content-type", "image/gif"},
+ 49: {"content-type", "image/jpeg"},
+ 50: {"content-type", "image/png"},
+ 51: {"content-type", "text/css"},
+ 52: {"content-type", "text/html; charset=utf-8"},
+ 53: {"content-type", "text/plain"},
+ 54: {"content-type", "text/plain;charset=utf-8"},
+ 55: {"range", "bytes=0-"},
+ 56: {"strict-transport-security", "max-age=31536000"},
+ 57: {"strict-transport-security", "max-age=31536000; includesubdomains"},
+ 58: {"strict-transport-security", "max-age=31536000; includesubdomains; preload"},
+ 59: {"vary", "accept-encoding"},
+ 60: {"vary", "origin"},
+ 61: {"x-content-type-options", "nosniff"},
+ 62: {"x-xss-protection", "1; mode=block"},
+ 63: {":status", "100"},
+ 64: {":status", "204"},
+ 65: {":status", "206"},
+ 66: {":status", "302"},
+ 67: {":status", "400"},
+ 68: {":status", "403"},
+ 69: {":status", "421"},
+ 70: {":status", "425"},
+ 71: {":status", "500"},
+ 72: {"accept-language", ""},
+ 73: {"access-control-allow-credentials", "FALSE"},
+ 74: {"access-control-allow-credentials", "TRUE"},
+ 75: {"access-control-allow-headers", "*"},
+ 76: {"access-control-allow-methods", "get"},
+ 77: {"access-control-allow-methods", "get, post, options"},
+ 78: {"access-control-allow-methods", "options"},
+ 79: {"access-control-expose-headers", "content-length"},
+ 80: {"access-control-request-headers", "content-type"},
+ 81: {"access-control-request-method", "get"},
+ 82: {"access-control-request-method", "post"},
+ 83: {"alt-svc", "clear"},
+ 84: {"authorization", ""},
+ 85: {"content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"},
+ 86: {"early-data", "1"},
+ 87: {"expect-ct", ""},
+ 88: {"forwarded", ""},
+ 89: {"if-range", ""},
+ 90: {"origin", ""},
+ 91: {"purpose", "prefetch"},
+ 92: {"server", ""},
+ 93: {"timing-allow-origin", "*"},
+ 94: {"upgrade-insecure-requests", "1"},
+ 95: {"user-agent", ""},
+ 96: {"x-forwarded-for", ""},
+ 97: {"x-frame-options", "deny"},
+ 98: {"x-frame-options", "sameorigin"},
+}
diff --git a/internal/http3/qpack_test.go b/internal/http3/qpack_test.go
new file mode 100644
index 0000000000..6e16511fc6
--- /dev/null
+++ b/internal/http3/qpack_test.go
@@ -0,0 +1,173 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestPrefixedInt(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ for _, test := range []struct {
+ value int64
+ prefixLen uint8
+ encoded []byte
+ }{
+ // https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.1
+ {
+ value: 10,
+ prefixLen: 5,
+ encoded: []byte{
+ 0b_0000_1010,
+ },
+ },
+ // https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.2
+ {
+ value: 1337,
+ prefixLen: 5,
+ encoded: []byte{
+ 0b0001_1111,
+ 0b1001_1010,
+ 0b0000_1010,
+ },
+ },
+ // https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.3
+ {
+ value: 42,
+ prefixLen: 8,
+ encoded: []byte{
+ 0b0010_1010,
+ },
+ },
+ } {
+ highBitMask := ^((byte(1) << test.prefixLen) - 1)
+ for _, highBits := range []byte{
+ 0, highBitMask, 0b1010_1010 & highBitMask,
+ } {
+ gotEnc := appendPrefixedInt(nil, highBits, test.prefixLen, test.value)
+ wantEnc := append([]byte{}, test.encoded...)
+ wantEnc[0] |= highBits
+ if !bytes.Equal(gotEnc, wantEnc) {
+ t.Errorf("appendPrefixedInt(nil, 0b%08b, %v, %v) = {%x}, want {%x}",
+ highBits, test.prefixLen, test.value, gotEnc, wantEnc)
+ }
+
+ st1.Write(gotEnc)
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ gotFirstByte, v, err := st2.readPrefixedInt(test.prefixLen)
+ if err != nil || gotFirstByte&highBitMask != highBits || v != test.value {
+ t.Errorf("st.readPrefixedInt(%v) = 0b%08b, %v, %v; want 0b%08b, %v, nil", test.prefixLen, gotFirstByte, v, err, highBits, test.value)
+ }
+ }
+ }
+}
+
+func TestPrefixedString(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ for _, test := range []struct {
+ value string
+ prefixLen uint8
+ encoded []byte
+ }{
+ // https://www.rfc-editor.org/rfc/rfc7541#appendix-C.6.1
+ {
+ value: "302",
+ prefixLen: 7,
+ encoded: []byte{
+ 0x82, // H bit + length 2
+ 0x64, 0x02,
+ },
+ },
+ {
+ value: "private",
+ prefixLen: 5,
+ encoded: []byte{
+ 0x25, // H bit + length 5
+ 0xae, 0xc3, 0x77, 0x1a, 0x4b,
+ },
+ },
+ {
+ value: "Mon, 21 Oct 2013 20:13:21 GMT",
+ prefixLen: 7,
+ encoded: []byte{
+ 0x96, // H bit + length 22
+ 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44,
+ 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66,
+ 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff,
+ },
+ },
+ {
+ value: "https://www.example.com",
+ prefixLen: 7,
+ encoded: []byte{
+ 0x91, // H bit + length 17
+ 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f,
+ 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43,
+ 0xd3,
+ },
+ },
+ // Not Huffman encoded (encoded size == unencoded size).
+ {
+ value: "a",
+ prefixLen: 7,
+ encoded: []byte{
+ 0x01, // length 1
+ 0x61,
+ },
+ },
+ // Empty string.
+ {
+ value: "",
+ prefixLen: 7,
+ encoded: []byte{
+ 0x00, // length 0
+ },
+ },
+ } {
+ highBitMask := ^((byte(1) << (test.prefixLen + 1)) - 1)
+ for _, highBits := range []byte{
+ 0, highBitMask, 0b1010_1010 & highBitMask,
+ } {
+ gotEnc := appendPrefixedString(nil, highBits, test.prefixLen, test.value)
+ wantEnc := append([]byte{}, test.encoded...)
+ wantEnc[0] |= highBits
+ if !bytes.Equal(gotEnc, wantEnc) {
+ t.Errorf("appendPrefixedString(nil, 0b%08b, %v, %v) = {%x}, want {%x}",
+ highBits, test.prefixLen, test.value, gotEnc, wantEnc)
+ }
+
+ st1.Write(gotEnc)
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ gotFirstByte, v, err := st2.readPrefixedString(test.prefixLen)
+ if err != nil || gotFirstByte&highBitMask != highBits || v != test.value {
+ t.Errorf("st.readPrefixedInt(%v) = 0b%08b, %q, %v; want 0b%08b, %q, nil", test.prefixLen, gotFirstByte, v, err, highBits, test.value)
+ }
+ }
+ }
+}
+
+func TestHuffmanDecodingFailure(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ st1.Write([]byte{
+ 0x82, // H bit + length 4
+ 0b_1111_1111,
+ 0b_1111_1111,
+ 0b_1111_1111,
+ 0b_1111_1111,
+ })
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ if b, v, err := st2.readPrefixedString(7); err == nil {
+ t.Fatalf("readPrefixedString(7) = %x, %v, nil; want error", b, v)
+ }
+}
diff --git a/internal/http3/quic.go b/internal/http3/quic.go
new file mode 100644
index 0000000000..6d2b120094
--- /dev/null
+++ b/internal/http3/quic.go
@@ -0,0 +1,42 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "crypto/tls"
+
+ "golang.org/x/net/quic"
+)
+
+func initConfig(config *quic.Config) *quic.Config {
+ if config == nil {
+ config = &quic.Config{}
+ }
+
+ // maybeCloneTLSConfig clones the user-provided tls.Config (but only once)
+ // prior to us modifying it.
+ needCloneTLSConfig := true
+ maybeCloneTLSConfig := func() *tls.Config {
+ if needCloneTLSConfig {
+ config.TLSConfig = config.TLSConfig.Clone()
+ needCloneTLSConfig = false
+ }
+ return config.TLSConfig
+ }
+
+ if config.TLSConfig == nil {
+ config.TLSConfig = &tls.Config{}
+ needCloneTLSConfig = false
+ }
+ if config.TLSConfig.MinVersion == 0 {
+ maybeCloneTLSConfig().MinVersion = tls.VersionTLS13
+ }
+ if config.TLSConfig.NextProtos == nil {
+ maybeCloneTLSConfig().NextProtos = []string{"h3"}
+ }
+ return config
+}
diff --git a/internal/http3/quic_test.go b/internal/http3/quic_test.go
new file mode 100644
index 0000000000..bc3b110fe9
--- /dev/null
+++ b/internal/http3/quic_test.go
@@ -0,0 +1,234 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "net"
+ "net/netip"
+ "runtime"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/net/internal/gate"
+ "golang.org/x/net/internal/testcert"
+ "golang.org/x/net/quic"
+)
+
+// newLocalQUICEndpoint returns a QUIC Endpoint listening on localhost.
+func newLocalQUICEndpoint(t *testing.T) *quic.Endpoint {
+ t.Helper()
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("ReadMsgUDP not supported on %s", runtime.GOOS)
+ }
+ conf := &quic.Config{
+ TLSConfig: testTLSConfig,
+ }
+ e, err := quic.Listen("udp", "127.0.0.1:0", conf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ e.Close(context.Background())
+ })
+ return e
+}
+
+// newQUICEndpointPair returns two QUIC endpoints on the same test network.
+func newQUICEndpointPair(t testing.TB) (e1, e2 *quic.Endpoint) {
+ config := &quic.Config{
+ TLSConfig: testTLSConfig,
+ }
+ tn := &testNet{}
+ e1 = tn.newQUICEndpoint(t, config)
+ e2 = tn.newQUICEndpoint(t, config)
+ return e1, e2
+}
+
+// newQUICStreamPair returns the two sides of a bidirectional QUIC stream.
+func newQUICStreamPair(t testing.TB) (s1, s2 *quic.Stream) {
+ t.Helper()
+ config := &quic.Config{
+ TLSConfig: testTLSConfig,
+ }
+ e1, e2 := newQUICEndpointPair(t)
+ c1, err := e1.Dial(context.Background(), "udp", e2.LocalAddr().String(), config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ c2, err := e2.Accept(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ s1, err = c1.NewStream(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ s1.Flush()
+ s2, err = c2.AcceptStream(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ return s1, s2
+}
+
+// A testNet is a fake network of net.PacketConns.
+type testNet struct {
+ mu sync.Mutex
+ conns map[netip.AddrPort]*testPacketConn
+}
+
+// newPacketConn returns a new PacketConn with a unique source address.
+func (tn *testNet) newPacketConn() *testPacketConn {
+ tn.mu.Lock()
+ defer tn.mu.Unlock()
+ if tn.conns == nil {
+ tn.conns = make(map[netip.AddrPort]*testPacketConn)
+ }
+ localAddr := netip.AddrPortFrom(
+ netip.AddrFrom4([4]byte{
+ 127, 0, 0, byte(len(tn.conns)),
+ }),
+ 443)
+ tc := &testPacketConn{
+ tn: tn,
+ localAddr: localAddr,
+ gate: gate.New(false),
+ }
+ tn.conns[localAddr] = tc
+ return tc
+}
+
+func (tn *testNet) newQUICEndpoint(t testing.TB, config *quic.Config) *quic.Endpoint {
+ t.Helper()
+ pc := tn.newPacketConn()
+ e, err := quic.NewEndpoint(pc, config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ e.Close(t.Context())
+ })
+ return e
+}
+
+// connForAddr returns the conn with the given source address.
+func (tn *testNet) connForAddr(srcAddr netip.AddrPort) *testPacketConn {
+ tn.mu.Lock()
+ defer tn.mu.Unlock()
+ return tn.conns[srcAddr]
+}
+
+// A testPacketConn is a net.PacketConn on a testNet fake network.
+type testPacketConn struct {
+ tn *testNet
+ localAddr netip.AddrPort
+
+ gate gate.Gate
+ queue []testPacket
+ closed bool
+}
+
+type testPacket struct {
+ b []byte
+ src netip.AddrPort
+}
+
+func (tc *testPacketConn) unlock() {
+ tc.gate.Unlock(tc.closed || len(tc.queue) > 0)
+}
+
+func (tc *testPacketConn) ReadFrom(p []byte) (n int, srcAddr net.Addr, err error) {
+ if err := tc.gate.WaitAndLock(context.Background()); err != nil {
+ return 0, nil, err
+ }
+ defer tc.unlock()
+ if tc.closed {
+ return 0, nil, net.ErrClosed
+ }
+ n = copy(p, tc.queue[0].b)
+ srcAddr = net.UDPAddrFromAddrPort(tc.queue[0].src)
+ tc.queue = tc.queue[1:]
+ return n, srcAddr, nil
+}
+
+func (tc *testPacketConn) WriteTo(p []byte, dstAddr net.Addr) (n int, err error) {
+ tc.gate.Lock()
+ closed := tc.closed
+ tc.unlock()
+ if closed {
+ return 0, net.ErrClosed
+ }
+
+ ap, err := addrPortFromAddr(dstAddr)
+ if err != nil {
+ return 0, err
+ }
+ dst := tc.tn.connForAddr(ap)
+ if dst == nil {
+ return len(p), nil // sent into the void
+ }
+ dst.gate.Lock()
+ defer dst.unlock()
+ dst.queue = append(dst.queue, testPacket{
+ b: bytes.Clone(p),
+ src: tc.localAddr,
+ })
+ return len(p), nil
+}
+
+func (tc *testPacketConn) Close() error {
+ tc.tn.mu.Lock()
+ tc.tn.conns[tc.localAddr] = nil
+ tc.tn.mu.Unlock()
+
+ tc.gate.Lock()
+ defer tc.unlock()
+ tc.closed = true
+ tc.queue = nil
+ return nil
+}
+
+func (tc *testPacketConn) LocalAddr() net.Addr {
+ return net.UDPAddrFromAddrPort(tc.localAddr)
+}
+
+func (tc *testPacketConn) SetDeadline(time.Time) error { panic("unimplemented") }
+func (tc *testPacketConn) SetReadDeadline(time.Time) error { panic("unimplemented") }
+func (tc *testPacketConn) SetWriteDeadline(time.Time) error { panic("unimplemented") }
+
+func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
+ switch a := addr.(type) {
+ case *net.UDPAddr:
+ return a.AddrPort(), nil
+ }
+ return netip.ParseAddrPort(addr.String())
+}
+
+var testTLSConfig = &tls.Config{
+ InsecureSkipVerify: true,
+ CipherSuites: []uint16{
+ tls.TLS_AES_128_GCM_SHA256,
+ tls.TLS_AES_256_GCM_SHA384,
+ tls.TLS_CHACHA20_POLY1305_SHA256,
+ },
+ MinVersion: tls.VersionTLS13,
+ Certificates: []tls.Certificate{testCert},
+ NextProtos: []string{"h3"},
+}
+
+var testCert = func() tls.Certificate {
+ cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
+ if err != nil {
+ panic(err)
+ }
+ return cert
+}()
diff --git a/internal/http3/roundtrip.go b/internal/http3/roundtrip.go
new file mode 100644
index 0000000000..bf55a13159
--- /dev/null
+++ b/internal/http3/roundtrip.go
@@ -0,0 +1,347 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "errors"
+ "io"
+ "net/http"
+ "strconv"
+ "sync"
+
+ "golang.org/x/net/internal/httpcommon"
+)
+
+type roundTripState struct {
+ cc *ClientConn
+ st *stream
+
+ // Request body, provided by the caller.
+ onceCloseReqBody sync.Once
+ reqBody io.ReadCloser
+
+ reqBodyWriter bodyWriter
+
+ // Response.Body, provided to the caller.
+ respBody bodyReader
+
+ errOnce sync.Once
+ err error
+}
+
+// abort terminates the RoundTrip.
+// It returns the first fatal error encountered by the RoundTrip call.
+func (rt *roundTripState) abort(err error) error {
+ rt.errOnce.Do(func() {
+ rt.err = err
+ switch e := err.(type) {
+ case *connectionError:
+ rt.cc.abort(e)
+ case *streamError:
+ rt.st.stream.CloseRead()
+ rt.st.stream.Reset(uint64(e.code))
+ default:
+ rt.st.stream.CloseRead()
+ rt.st.stream.Reset(uint64(errH3NoError))
+ }
+ })
+ return rt.err
+}
+
+// closeReqBody closes the Request.Body, at most once.
+func (rt *roundTripState) closeReqBody() {
+ if rt.reqBody != nil {
+ rt.onceCloseReqBody.Do(func() {
+ rt.reqBody.Close()
+ })
+ }
+}
+
+// RoundTrip sends a request on the connection.
+func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error) {
+ // Each request gets its own QUIC stream.
+ st, err := newConnStream(req.Context(), cc.qconn, streamTypeRequest)
+ if err != nil {
+ return nil, err
+ }
+ rt := &roundTripState{
+ cc: cc,
+ st: st,
+ }
+ defer func() {
+ if err != nil {
+ err = rt.abort(err)
+ }
+ }()
+
+ // Cancel reads/writes on the stream when the request expires.
+ st.stream.SetReadContext(req.Context())
+ st.stream.SetWriteContext(req.Context())
+
+ contentLength := actualContentLength(req)
+
+ var encr httpcommon.EncodeHeadersResult
+ headers := cc.enc.encode(func(yield func(itype indexType, name, value string)) {
+ encr, err = httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
+ Request: httpcommon.Request{
+ URL: req.URL,
+ Method: req.Method,
+ Host: req.Host,
+ Header: req.Header,
+ Trailer: req.Trailer,
+ ActualContentLength: contentLength,
+ },
+ AddGzipHeader: false, // TODO: add when appropriate
+ PeerMaxHeaderListSize: 0,
+ DefaultUserAgent: "Go-http-client/3",
+ }, func(name, value string) {
+ // Issue #71374: Consider supporting never-indexed fields.
+ yield(mayIndex, name, value)
+ })
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Write the HEADERS frame.
+ st.writeVarint(int64(frameTypeHeaders))
+ st.writeVarint(int64(len(headers)))
+ st.Write(headers)
+ if err := st.Flush(); err != nil {
+ return nil, err
+ }
+
+ if encr.HasBody {
+ // TODO: Defer sending the request body when "Expect: 100-continue" is set.
+ rt.reqBody = req.Body
+ rt.reqBodyWriter.st = st
+ rt.reqBodyWriter.remain = contentLength
+ rt.reqBodyWriter.flush = true
+ rt.reqBodyWriter.name = "request"
+ go copyRequestBody(rt)
+ }
+
+ // Read the response headers.
+ for {
+ ftype, err := st.readFrameHeader()
+ if err != nil {
+ return nil, err
+ }
+ switch ftype {
+ case frameTypeHeaders:
+ statusCode, h, err := cc.handleHeaders(st)
+ if err != nil {
+ return nil, err
+ }
+
+ if statusCode >= 100 && statusCode < 199 {
+ // TODO: Handle 1xx responses.
+ continue
+ }
+
+ // We have the response headers.
+ // Set up the response and return it to the caller.
+ contentLength, err := parseResponseContentLength(req.Method, statusCode, h)
+ if err != nil {
+ return nil, err
+ }
+ rt.respBody.st = st
+ rt.respBody.remain = contentLength
+ resp := &http.Response{
+ Proto: "HTTP/3.0",
+ ProtoMajor: 3,
+ Header: h,
+ StatusCode: statusCode,
+ Status: strconv.Itoa(statusCode) + " " + http.StatusText(statusCode),
+ ContentLength: contentLength,
+ Body: (*transportResponseBody)(rt),
+ }
+ // TODO: Automatic Content-Type: gzip decoding.
+ return resp, nil
+ case frameTypePushPromise:
+ if err := cc.handlePushPromise(st); err != nil {
+ return nil, err
+ }
+ default:
+ if err := st.discardUnknownFrame(ftype); err != nil {
+ return nil, err
+ }
+ }
+ }
+}
+
+// actualContentLength returns a sanitized version of req.ContentLength,
+// where 0 actually means zero (not unknown) and -1 means unknown.
+func actualContentLength(req *http.Request) int64 {
+ if req.Body == nil || req.Body == http.NoBody {
+ return 0
+ }
+ if req.ContentLength != 0 {
+ return req.ContentLength
+ }
+ return -1
+}
+
+func copyRequestBody(rt *roundTripState) {
+ defer rt.closeReqBody()
+ _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody)
+ if closeErr := rt.reqBodyWriter.Close(); err == nil {
+ err = closeErr
+ }
+ if err != nil {
+ // Something went wrong writing the body.
+ rt.abort(err)
+ } else {
+ // We wrote the whole body.
+ rt.st.stream.CloseWrite()
+ }
+}
+
+// transportResponseBody is the Response.Body returned by RoundTrip.
+type transportResponseBody roundTripState
+
+// Read is Response.Body.Read.
+func (b *transportResponseBody) Read(p []byte) (n int, err error) {
+ return b.respBody.Read(p)
+}
+
+var errRespBodyClosed = errors.New("response body closed")
+
+// Close is Response.Body.Close.
+// Closing the response body is how the caller signals that they're done with a request.
+func (b *transportResponseBody) Close() error {
+ rt := (*roundTripState)(b)
+ // Close the request body, which should wake up copyRequestBody if it's
+ // currently blocked reading the body.
+ rt.closeReqBody()
+ // Close the request stream, since we're done with the request.
+ // Reset closes the sending half of the stream.
+ rt.st.stream.Reset(uint64(errH3NoError))
+ // respBody.Close is responsible for closing the receiving half.
+ err := rt.respBody.Close()
+ if err == nil {
+ err = errRespBodyClosed
+ }
+ err = rt.abort(err)
+ if err == errRespBodyClosed {
+ // No other errors occurred before closing Response.Body,
+ // so consider this a successful request.
+ return nil
+ }
+ return err
+}
+
+func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) {
+ clens := h["Content-Length"]
+ if len(clens) == 0 {
+ return -1, nil
+ }
+
+ // We allow duplicate Content-Length headers,
+ // but only if they all have the same value.
+ for _, v := range clens[1:] {
+ if clens[0] != v {
+ return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"}
+ }
+ }
+
+ // "A server MUST NOT send a Content-Length header field in any response
+ // with a status code of 1xx (Informational) or 204 (No Content).
+ // A server MUST NOT send a Content-Length header field in any 2xx (Successful)
+ // response to a CONNECT request [...]"
+ // https://www.rfc-editor.org/rfc/rfc9110#section-8.6-8
+ if (statusCode >= 100 && statusCode < 200) ||
+ statusCode == 204 ||
+ (method == "CONNECT" && statusCode >= 200 && statusCode < 300) {
+ // This is a protocol violation, but a fairly harmless one.
+ // Just ignore the header.
+ return -1, nil
+ }
+
+ contentLen, err := strconv.ParseUint(clens[0], 10, 63)
+ if err != nil {
+ return -1, &streamError{errH3MessageError, "invalid Content-Length header"}
+ }
+ return int64(contentLen), nil
+}
+
+func (cc *ClientConn) handleHeaders(st *stream) (statusCode int, h http.Header, err error) {
+ haveStatus := false
+ cookie := ""
+ // Issue #71374: Consider tracking the never-indexed status of headers
+ // with the N bit set in their QPACK encoding.
+ err = cc.dec.decode(st, func(_ indexType, name, value string) error {
+ switch {
+ case name == ":status":
+ if haveStatus {
+ return &streamError{errH3MessageError, "duplicate :status"}
+ }
+ haveStatus = true
+ statusCode, err = strconv.Atoi(value)
+ if err != nil {
+ return &streamError{errH3MessageError, "invalid :status"}
+ }
+ case name[0] == ':':
+ // "Endpoints MUST treat a request or response
+ // that contains undefined or invalid
+ // pseudo-header fields as malformed."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3-3
+ return &streamError{errH3MessageError, "undefined pseudo-header"}
+ case name == "cookie":
+ // "If a decompressed field section contains multiple cookie field lines,
+ // these MUST be concatenated into a single byte string [...]"
+ // using the two-byte delimiter of "; "''
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
+ if cookie == "" {
+ cookie = value
+ } else {
+ cookie += "; " + value
+ }
+ default:
+ if h == nil {
+ h = make(http.Header)
+ }
+ // TODO: Use a per-connection canonicalization cache as we do in HTTP/2.
+ // Maybe we could put this in the QPACK decoder and have it deliver
+ // pre-canonicalized headers to us here?
+ cname := httpcommon.CanonicalHeader(name)
+ // TODO: Consider using a single []string slice for all headers,
+ // as we do in the HTTP/1 and HTTP/2 cases.
+ // This is a bit tricky, since we don't know the number of headers
+ // at the start of decoding. Perhaps it's worth doing a two-pass decode,
+ // or perhaps we should just allocate header value slices in
+ // reasonably-sized chunks.
+ h[cname] = append(h[cname], value)
+ }
+ return nil
+ })
+ if !haveStatus {
+ // "[The :status] pseudo-header field MUST be included in all responses [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3.2-1
+ err = errH3MessageError
+ }
+ if cookie != "" {
+ if h == nil {
+ h = make(http.Header)
+ }
+ h["Cookie"] = []string{cookie}
+ }
+ if err := st.endFrame(); err != nil {
+ return 0, nil, err
+ }
+ return statusCode, h, err
+}
+
+func (cc *ClientConn) handlePushPromise(st *stream) error {
+ // "A client MUST treat receipt of a PUSH_PROMISE frame that contains a
+ // larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
+ return &connectionError{
+ code: errH3IDError,
+ message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent",
+ }
+}
diff --git a/internal/http3/roundtrip_test.go b/internal/http3/roundtrip_test.go
new file mode 100644
index 0000000000..acd8613d0e
--- /dev/null
+++ b/internal/http3/roundtrip_test.go
@@ -0,0 +1,354 @@
+// 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.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "net/http"
+ "testing"
+ "testing/synctest"
+
+ "golang.org/x/net/quic"
+)
+
+func TestRoundTripSimple(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ req.Header["User-Agent"] = nil
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(http.Header{
+ ":authority": []string{"example.tld"},
+ ":method": []string{"GET"},
+ ":path": []string{"/"},
+ ":scheme": []string{"https"},
+ })
+ st.writeHeaders(http.Header{
+ ":status": []string{"200"},
+ "x-some-header": []string{"value"},
+ })
+ rt.wantStatus(200)
+ rt.wantHeaders(http.Header{
+ "X-Some-Header": []string{"value"},
+ })
+ })
+}
+
+func TestRoundTripWithBadHeaders(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ req.Header["Invalid\nHeader"] = []string{"x"}
+ rt := tc.roundTrip(req)
+ rt.wantError("RoundTrip fails when request contains invalid headers")
+ })
+}
+
+func TestRoundTripWithUnknownFrame(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+
+ // Write an unknown frame type before the response HEADERS.
+ data := "frame content"
+ st.writeVarint(0x1f + 0x21) // reserved frame type
+ st.writeVarint(int64(len(data))) // size
+ st.Write([]byte(data))
+
+ st.writeHeaders(http.Header{
+ ":status": []string{"200"},
+ })
+ rt.wantStatus(200)
+ })
+}
+
+func TestRoundTripWithInvalidPushPromise(t *testing.T) {
+ // "A client MUST treat receipt of a PUSH_PROMISE frame that contains
+ // a larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+
+ // Write a PUSH_PROMISE frame.
+ // Since the client hasn't indicated willingness to accept pushes,
+ // this is a connection error.
+ st.writePushPromise(0, http.Header{
+ ":path": []string{"/foo"},
+ })
+ rt.wantError("RoundTrip fails after receiving invalid PUSH_PROMISE")
+ tc.wantClosed(
+ "push ID exceeds client's MAX_PUSH_ID",
+ errH3IDError,
+ )
+ })
+}
+
+func TestRoundTripResponseContentLength(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ respHeader http.Header
+ wantContentLength int64
+ wantError bool
+ }{{
+ name: "valid",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ "content-length": []string{"100"},
+ },
+ wantContentLength: 100,
+ }, {
+ name: "absent",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ },
+ wantContentLength: -1,
+ }, {
+ name: "unparseable",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ "content-length": []string{"1 1"},
+ },
+ wantError: true,
+ }, {
+ name: "duplicated",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ "content-length": []string{"500", "500", "500"},
+ },
+ wantContentLength: 500,
+ }, {
+ name: "inconsistent",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ "content-length": []string{"1", "2"},
+ },
+ wantError: true,
+ }, {
+ // 204 responses aren't allowed to contain a Content-Length header.
+ // We just ignore it.
+ name: "204",
+ respHeader: http.Header{
+ ":status": []string{"204"},
+ "content-length": []string{"100"},
+ },
+ wantContentLength: -1,
+ }} {
+ runSynctestSubtest(t, test.name, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+ st.writeHeaders(test.respHeader)
+ if test.wantError {
+ rt.wantError("invalid content-length in response")
+ return
+ }
+ if got, want := rt.response().ContentLength, test.wantContentLength; got != want {
+ t.Errorf("Response.ContentLength = %v, want %v", got, want)
+ }
+ })
+ }
+}
+
+func TestRoundTripMalformedResponses(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ respHeader http.Header
+ }{{
+ name: "duplicate :status",
+ respHeader: http.Header{
+ ":status": []string{"200", "204"},
+ },
+ }, {
+ name: "unparseable :status",
+ respHeader: http.Header{
+ ":status": []string{"frogpants"},
+ },
+ }, {
+ name: "undefined pseudo-header",
+ respHeader: http.Header{
+ ":status": []string{"200"},
+ ":unknown": []string{"x"},
+ },
+ }, {
+ name: "no :status",
+ respHeader: http.Header{},
+ }} {
+ runSynctestSubtest(t, test.name, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+ st.writeHeaders(test.respHeader)
+ rt.wantError("malformed response")
+ })
+ }
+}
+
+func TestRoundTripCrumbledCookiesInResponse(t *testing.T) {
+ // "If a decompressed field section contains multiple cookie field lines,
+ // these MUST be concatenated into a single byte string [...]"
+ // using the two-byte delimiter of "; "''
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", nil)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+ st.writeHeaders(http.Header{
+ ":status": []string{"200"},
+ "cookie": []string{"a=1", "b=2; c=3", "d=4"},
+ })
+ rt.wantStatus(200)
+ rt.wantHeaders(http.Header{
+ "Cookie": []string{"a=1; b=2; c=3; d=4"},
+ })
+ })
+}
+
+func TestRoundTripRequestBodySent(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ bodyr, bodyw := io.Pipe()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+ st.wantHeaders(nil)
+
+ bodyw.Write([]byte{0, 1, 2, 3, 4})
+ st.wantData([]byte{0, 1, 2, 3, 4})
+
+ bodyw.Write([]byte{5, 6, 7})
+ st.wantData([]byte{5, 6, 7})
+
+ bodyw.Close()
+ st.wantClosed("request body sent")
+
+ st.writeHeaders(http.Header{
+ ":status": []string{"200"},
+ })
+ rt.wantStatus(200)
+ rt.response().Body.Close()
+ })
+}
+
+func TestRoundTripRequestBodyErrors(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ body io.Reader
+ contentLength int64
+ }{{
+ name: "too short",
+ contentLength: 10,
+ body: bytes.NewReader([]byte{0, 1, 2, 3, 4}),
+ }, {
+ name: "too long",
+ contentLength: 5,
+ body: bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}),
+ }, {
+ name: "read error",
+ body: io.MultiReader(
+ bytes.NewReader([]byte{0, 1, 2, 3, 4}),
+ &testReader{
+ readFunc: func([]byte) (int, error) {
+ return 0, errors.New("read error")
+ },
+ },
+ ),
+ }} {
+ runSynctestSubtest(t, test.name, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ req, _ := http.NewRequest("GET", "https://example.tld/", test.body)
+ req.ContentLength = test.contentLength
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+
+ // The Transport should send some number of frames before detecting an
+ // error in the request body and aborting the request.
+ synctest.Wait()
+ for {
+ _, err := st.readFrameHeader()
+ if err != nil {
+ var code quic.StreamErrorCode
+ if !errors.As(err, &code) {
+ t.Fatalf("request stream closed with error %v: want QUIC stream error", err)
+ }
+ break
+ }
+ if err := st.discardFrame(); err != nil {
+ t.Fatalf("discardFrame: %v", err)
+ }
+ }
+
+ // RoundTrip returns with an error.
+ rt.wantError("request fails due to body error")
+ })
+ }
+}
+
+func TestRoundTripRequestBodyErrorAfterHeaders(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+
+ bodyr, bodyw := io.Pipe()
+ req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
+ req.ContentLength = 10
+ rt := tc.roundTrip(req)
+ st := tc.wantStream(streamTypeRequest)
+
+ // Server sends response headers, and RoundTrip returns.
+ // The request body hasn't been sent yet.
+ st.wantHeaders(nil)
+ st.writeHeaders(http.Header{
+ ":status": []string{"200"},
+ })
+ rt.wantStatus(200)
+
+ // Write too many bytes to the request body, triggering a request error.
+ bodyw.Write(make([]byte, req.ContentLength+1))
+
+ //io.Copy(io.Discard, st)
+ st.wantError(quic.StreamErrorCode(errH3InternalError))
+
+ if err := rt.response().Body.Close(); err == nil {
+ t.Fatalf("Response.Body.Close() = %v, want error", err)
+ }
+ })
+}
diff --git a/internal/http3/server.go b/internal/http3/server.go
new file mode 100644
index 0000000000..ca93c5298a
--- /dev/null
+++ b/internal/http3/server.go
@@ -0,0 +1,172 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "context"
+ "net/http"
+ "sync"
+
+ "golang.org/x/net/quic"
+)
+
+// A Server is an HTTP/3 server.
+// The zero value for Server is a valid server.
+type Server struct {
+ // Handler to invoke for requests, http.DefaultServeMux if nil.
+ Handler http.Handler
+
+ // Config is the QUIC configuration used by the server.
+ // The Config may be nil.
+ //
+ // ListenAndServe may clone and modify the Config.
+ // The Config must not be modified after calling ListenAndServe.
+ Config *quic.Config
+
+ initOnce sync.Once
+}
+
+func (s *Server) init() {
+ s.initOnce.Do(func() {
+ s.Config = initConfig(s.Config)
+ if s.Handler == nil {
+ s.Handler = http.DefaultServeMux
+ }
+ })
+}
+
+// ListenAndServe listens on the UDP network address addr
+// and then calls Serve to handle requests on incoming connections.
+func (s *Server) ListenAndServe(addr string) error {
+ s.init()
+ e, err := quic.Listen("udp", addr, s.Config)
+ if err != nil {
+ return err
+ }
+ return s.Serve(e)
+}
+
+// Serve accepts incoming connections on the QUIC endpoint e,
+// and handles requests from those connections.
+func (s *Server) Serve(e *quic.Endpoint) error {
+ s.init()
+ for {
+ qconn, err := e.Accept(context.Background())
+ if err != nil {
+ return err
+ }
+ go newServerConn(qconn)
+ }
+}
+
+type serverConn struct {
+ qconn *quic.Conn
+
+ genericConn // for handleUnidirectionalStream
+ enc qpackEncoder
+ dec qpackDecoder
+}
+
+func newServerConn(qconn *quic.Conn) {
+ sc := &serverConn{
+ qconn: qconn,
+ }
+ sc.enc.init()
+
+ // Create control stream and send SETTINGS frame.
+ // TODO: Time out on creating stream.
+ controlStream, err := newConnStream(context.Background(), sc.qconn, streamTypeControl)
+ if err != nil {
+ return
+ }
+ controlStream.writeSettings()
+ controlStream.Flush()
+
+ sc.acceptStreams(sc.qconn, sc)
+}
+
+func (sc *serverConn) handleControlStream(st *stream) error {
+ // "A SETTINGS frame MUST be sent as the first frame of each control stream [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2
+ if err := st.readSettings(func(settingsType, settingsValue int64) error {
+ switch settingsType {
+ case settingsMaxFieldSectionSize:
+ _ = settingsValue // TODO
+ case settingsQPACKMaxTableCapacity:
+ _ = settingsValue // TODO
+ case settingsQPACKBlockedStreams:
+ _ = settingsValue // TODO
+ default:
+ // Unknown settings types are ignored.
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ for {
+ ftype, err := st.readFrameHeader()
+ if err != nil {
+ return err
+ }
+ switch ftype {
+ case frameTypeCancelPush:
+ // "If a server receives a CANCEL_PUSH frame for a push ID
+ // that has not yet been mentioned by a PUSH_PROMISE frame,
+ // this MUST be treated as a connection error of type H3_ID_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-8
+ return &connectionError{
+ code: errH3IDError,
+ message: "CANCEL_PUSH for unsent push ID",
+ }
+ case frameTypeGoaway:
+ return errH3NoError
+ default:
+ // Unknown frames are ignored.
+ if err := st.discardUnknownFrame(ftype); err != nil {
+ return err
+ }
+ }
+ }
+}
+
+func (sc *serverConn) handleEncoderStream(*stream) error {
+ // TODO
+ return nil
+}
+
+func (sc *serverConn) handleDecoderStream(*stream) error {
+ // TODO
+ return nil
+}
+
+func (sc *serverConn) handlePushStream(*stream) error {
+ // "[...] if a server receives a client-initiated push stream,
+ // this MUST be treated as a connection error of type H3_STREAM_CREATION_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2.2-3
+ return &connectionError{
+ code: errH3StreamCreationError,
+ message: "client created push stream",
+ }
+}
+
+func (sc *serverConn) handleRequestStream(st *stream) error {
+ // TODO
+ return nil
+}
+
+// abort closes the connection with an error.
+func (sc *serverConn) abort(err error) {
+ if e, ok := err.(*connectionError); ok {
+ sc.qconn.Abort(&quic.ApplicationError{
+ Code: uint64(e.code),
+ Reason: e.message,
+ })
+ } else {
+ sc.qconn.Abort(err)
+ }
+}
diff --git a/internal/http3/server_test.go b/internal/http3/server_test.go
new file mode 100644
index 0000000000..8e727d2512
--- /dev/null
+++ b/internal/http3/server_test.go
@@ -0,0 +1,110 @@
+// 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.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "net/netip"
+ "testing"
+ "testing/synctest"
+
+ "golang.org/x/net/internal/quic/quicwire"
+ "golang.org/x/net/quic"
+)
+
+func TestServerReceivePushStream(t *testing.T) {
+ // "[...] if a server receives a client-initiated push stream,
+ // this MUST be treated as a connection error of type H3_STREAM_CREATION_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2.2-3
+ runSynctest(t, func(t testing.TB) {
+ ts := newTestServer(t)
+ tc := ts.connect()
+ tc.newStream(streamTypePush)
+ tc.wantClosed("invalid client-created push stream", errH3StreamCreationError)
+ })
+}
+
+func TestServerCancelPushForUnsentPromise(t *testing.T) {
+ runSynctest(t, func(t testing.TB) {
+ ts := newTestServer(t)
+ tc := ts.connect()
+ tc.greet()
+
+ const pushID = 100
+ tc.control.writeVarint(int64(frameTypeCancelPush))
+ tc.control.writeVarint(int64(quicwire.SizeVarint(pushID)))
+ tc.control.writeVarint(pushID)
+ tc.control.Flush()
+
+ tc.wantClosed("client canceled never-sent push ID", errH3IDError)
+ })
+}
+
+type testServer struct {
+ t testing.TB
+ s *Server
+ tn testNet
+ *testQUICEndpoint
+
+ addr netip.AddrPort
+}
+
+type testQUICEndpoint struct {
+ t testing.TB
+ e *quic.Endpoint
+}
+
+func (te *testQUICEndpoint) dial() {
+}
+
+type testServerConn struct {
+ ts *testServer
+
+ *testQUICConn
+ control *testQUICStream
+}
+
+func newTestServer(t testing.TB) *testServer {
+ t.Helper()
+ ts := &testServer{
+ t: t,
+ s: &Server{
+ Config: &quic.Config{
+ TLSConfig: testTLSConfig,
+ },
+ },
+ }
+ e := ts.tn.newQUICEndpoint(t, ts.s.Config)
+ ts.addr = e.LocalAddr()
+ go ts.s.Serve(e)
+ return ts
+}
+
+func (ts *testServer) connect() *testServerConn {
+ ts.t.Helper()
+ config := &quic.Config{TLSConfig: testTLSConfig}
+ e := ts.tn.newQUICEndpoint(ts.t, nil)
+ qconn, err := e.Dial(ts.t.Context(), "udp", ts.addr.String(), config)
+ if err != nil {
+ ts.t.Fatal(err)
+ }
+ tc := &testServerConn{
+ ts: ts,
+ testQUICConn: newTestQUICConn(ts.t, qconn),
+ }
+ synctest.Wait()
+ return tc
+}
+
+// greet performs initial connection handshaking with the server.
+func (tc *testServerConn) greet() {
+ // Client creates a control stream.
+ tc.control = tc.newStream(streamTypeControl)
+ tc.control.writeVarint(int64(frameTypeSettings))
+ tc.control.writeVarint(0) // size
+ tc.control.Flush()
+ synctest.Wait()
+}
diff --git a/internal/http3/settings.go b/internal/http3/settings.go
new file mode 100644
index 0000000000..b5e562ecad
--- /dev/null
+++ b/internal/http3/settings.go
@@ -0,0 +1,72 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "golang.org/x/net/internal/quic/quicwire"
+)
+
+const (
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1
+ settingsMaxFieldSectionSize = 0x06
+
+ // https://www.rfc-editor.org/rfc/rfc9204.html#section-5
+ settingsQPACKMaxTableCapacity = 0x01
+ settingsQPACKBlockedStreams = 0x07
+)
+
+// writeSettings writes a complete SETTINGS frame.
+// Its parameter is a list of alternating setting types and values.
+func (st *stream) writeSettings(settings ...int64) {
+ var size int64
+ for _, s := range settings {
+ // Settings values that don't fit in a QUIC varint ([0,2^62)) will panic here.
+ size += int64(quicwire.SizeVarint(uint64(s)))
+ }
+ st.writeVarint(int64(frameTypeSettings))
+ st.writeVarint(size)
+ for _, s := range settings {
+ st.writeVarint(s)
+ }
+}
+
+// readSettings reads a complete SETTINGS frame, including the frame header.
+func (st *stream) readSettings(f func(settingType, value int64) error) error {
+ frameType, err := st.readFrameHeader()
+ if err != nil || frameType != frameTypeSettings {
+ return &connectionError{
+ code: errH3MissingSettings,
+ message: "settings not sent on control stream",
+ }
+ }
+ for st.lim > 0 {
+ settingsType, err := st.readVarint()
+ if err != nil {
+ return err
+ }
+ settingsValue, err := st.readVarint()
+ if err != nil {
+ return err
+ }
+
+ // Use of HTTP/2 settings where there is no corresponding HTTP/3 setting
+ // is an error.
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-5
+ switch settingsType {
+ case 0x02, 0x03, 0x04, 0x05:
+ return &connectionError{
+ code: errH3SettingsError,
+ message: "use of reserved setting",
+ }
+ }
+
+ if err := f(settingsType, settingsValue); err != nil {
+ return err
+ }
+ }
+ return st.endFrame()
+}
diff --git a/internal/http3/stream.go b/internal/http3/stream.go
new file mode 100644
index 0000000000..0f975407be
--- /dev/null
+++ b/internal/http3/stream.go
@@ -0,0 +1,262 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "context"
+ "io"
+
+ "golang.org/x/net/quic"
+)
+
+// A stream wraps a QUIC stream, providing methods to read/write various values.
+type stream struct {
+ stream *quic.Stream
+
+ // lim is the current read limit.
+ // Reading a frame header sets the limit to the end of the frame.
+ // Reading past the limit or reading less than the limit and ending the frame
+ // results in an error.
+ // -1 indicates no limit.
+ lim int64
+}
+
+// newConnStream creates a new stream on a connection.
+// It writes the stream header for unidirectional streams.
+//
+// The stream returned by newStream is not flushed,
+// and will not be sent to the peer until the caller calls
+// Flush or writes enough data to the stream.
+func newConnStream(ctx context.Context, qconn *quic.Conn, stype streamType) (*stream, error) {
+ var qs *quic.Stream
+ var err error
+ if stype == streamTypeRequest {
+ // Request streams are bidirectional.
+ qs, err = qconn.NewStream(ctx)
+ } else {
+ // All other streams are unidirectional.
+ qs, err = qconn.NewSendOnlyStream(ctx)
+ }
+ if err != nil {
+ return nil, err
+ }
+ st := &stream{
+ stream: qs,
+ lim: -1, // no limit
+ }
+ if stype != streamTypeRequest {
+ // Unidirectional stream header.
+ st.writeVarint(int64(stype))
+ }
+ return st, err
+}
+
+func newStream(qs *quic.Stream) *stream {
+ return &stream{
+ stream: qs,
+ lim: -1, // no limit
+ }
+}
+
+// readFrameHeader reads the type and length fields of an HTTP/3 frame.
+// It sets the read limit to the end of the frame.
+//
+// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.1
+func (st *stream) readFrameHeader() (ftype frameType, err error) {
+ if st.lim >= 0 {
+ // We shoudn't call readFrameHeader before ending the previous frame.
+ return 0, errH3FrameError
+ }
+ ftype, err = readVarint[frameType](st)
+ if err != nil {
+ return 0, err
+ }
+ size, err := st.readVarint()
+ if err != nil {
+ return 0, err
+ }
+ st.lim = size
+ return ftype, nil
+}
+
+// endFrame is called after reading a frame to reset the read limit.
+// It returns an error if the entire contents of a frame have not been read.
+func (st *stream) endFrame() error {
+ if st.lim != 0 {
+ return &connectionError{
+ code: errH3FrameError,
+ message: "invalid HTTP/3 frame",
+ }
+ }
+ st.lim = -1
+ return nil
+}
+
+// readFrameData returns the remaining data in the current frame.
+func (st *stream) readFrameData() ([]byte, error) {
+ if st.lim < 0 {
+ return nil, errH3FrameError
+ }
+ // TODO: Pool buffers to avoid allocation here.
+ b := make([]byte, st.lim)
+ _, err := io.ReadFull(st, b)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+// ReadByte reads one byte from the stream.
+func (st *stream) ReadByte() (b byte, err error) {
+ if err := st.recordBytesRead(1); err != nil {
+ return 0, err
+ }
+ b, err = st.stream.ReadByte()
+ if err != nil {
+ if err == io.EOF && st.lim < 0 {
+ return 0, io.EOF
+ }
+ return 0, errH3FrameError
+ }
+ return b, nil
+}
+
+// Read reads from the stream.
+func (st *stream) Read(b []byte) (int, error) {
+ n, err := st.stream.Read(b)
+ if e2 := st.recordBytesRead(n); e2 != nil {
+ return 0, e2
+ }
+ if err == io.EOF {
+ if st.lim == 0 {
+ // EOF at end of frame, ignore.
+ return n, nil
+ } else if st.lim > 0 {
+ // EOF inside frame, error.
+ return 0, errH3FrameError
+ } else {
+ // EOF outside of frame, surface to caller.
+ return n, io.EOF
+ }
+ }
+ if err != nil {
+ return 0, errH3FrameError
+ }
+ return n, nil
+}
+
+// discardUnknownFrame discards an unknown frame.
+//
+// HTTP/3 requires that unknown frames be ignored on all streams.
+// However, a known frame appearing in an unexpected place is a fatal error,
+// so this returns an error if the frame is one we know.
+func (st *stream) discardUnknownFrame(ftype frameType) error {
+ switch ftype {
+ case frameTypeData,
+ frameTypeHeaders,
+ frameTypeCancelPush,
+ frameTypeSettings,
+ frameTypePushPromise,
+ frameTypeGoaway,
+ frameTypeMaxPushID:
+ return &connectionError{
+ code: errH3FrameUnexpected,
+ message: "unexpected " + ftype.String() + " frame",
+ }
+ }
+ return st.discardFrame()
+}
+
+// discardFrame discards any remaining data in the current frame and resets the read limit.
+func (st *stream) discardFrame() error {
+ // TODO: Consider adding a *quic.Stream method to discard some amount of data.
+ for range st.lim {
+ _, err := st.stream.ReadByte()
+ if err != nil {
+ return &streamError{errH3FrameError, err.Error()}
+ }
+ }
+ st.lim = -1
+ return nil
+}
+
+// Write writes to the stream.
+func (st *stream) Write(b []byte) (int, error) { return st.stream.Write(b) }
+
+// Flush commits data written to the stream.
+func (st *stream) Flush() error { return st.stream.Flush() }
+
+// readVarint reads a QUIC variable-length integer from the stream.
+func (st *stream) readVarint() (v int64, err error) {
+ b, err := st.stream.ReadByte()
+ if err != nil {
+ return 0, err
+ }
+ v = int64(b & 0x3f)
+ n := 1 << (b >> 6)
+ for i := 1; i < n; i++ {
+ b, err := st.stream.ReadByte()
+ if err != nil {
+ return 0, errH3FrameError
+ }
+ v = (v << 8) | int64(b)
+ }
+ if err := st.recordBytesRead(n); err != nil {
+ return 0, err
+ }
+ return v, nil
+}
+
+// readVarint reads a varint of a particular type.
+func readVarint[T ~int64 | ~uint64](st *stream) (T, error) {
+ v, err := st.readVarint()
+ return T(v), err
+}
+
+// writeVarint writes a QUIC variable-length integer to the stream.
+func (st *stream) writeVarint(v int64) {
+ switch {
+ case v <= (1<<6)-1:
+ st.stream.WriteByte(byte(v))
+ case v <= (1<<14)-1:
+ st.stream.WriteByte((1 << 6) | byte(v>>8))
+ st.stream.WriteByte(byte(v))
+ case v <= (1<<30)-1:
+ st.stream.WriteByte((2 << 6) | byte(v>>24))
+ st.stream.WriteByte(byte(v >> 16))
+ st.stream.WriteByte(byte(v >> 8))
+ st.stream.WriteByte(byte(v))
+ case v <= (1<<62)-1:
+ st.stream.WriteByte((3 << 6) | byte(v>>56))
+ st.stream.WriteByte(byte(v >> 48))
+ st.stream.WriteByte(byte(v >> 40))
+ st.stream.WriteByte(byte(v >> 32))
+ st.stream.WriteByte(byte(v >> 24))
+ st.stream.WriteByte(byte(v >> 16))
+ st.stream.WriteByte(byte(v >> 8))
+ st.stream.WriteByte(byte(v))
+ default:
+ panic("varint too large")
+ }
+}
+
+// recordBytesRead records that n bytes have been read.
+// It returns an error if the read passes the current limit.
+func (st *stream) recordBytesRead(n int) error {
+ if st.lim < 0 {
+ return nil
+ }
+ st.lim -= int64(n)
+ if st.lim < 0 {
+ st.stream = nil // panic if we try to read again
+ return &connectionError{
+ code: errH3FrameError,
+ message: "invalid HTTP/3 frame",
+ }
+ }
+ return nil
+}
diff --git a/internal/http3/stream_test.go b/internal/http3/stream_test.go
new file mode 100644
index 0000000000..12b281c558
--- /dev/null
+++ b/internal/http3/stream_test.go
@@ -0,0 +1,319 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "testing"
+
+ "golang.org/x/net/internal/quic/quicwire"
+)
+
+func TestStreamReadVarint(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ for _, b := range [][]byte{
+ {0x00},
+ {0x3f},
+ {0x40, 0x00},
+ {0x7f, 0xff},
+ {0x80, 0x00, 0x00, 0x00},
+ {0xbf, 0xff, 0xff, 0xff},
+ {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+ // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1
+ {0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c},
+ {0x9d, 0x7f, 0x3e, 0x7d},
+ {0x7b, 0xbd},
+ {0x25},
+ {0x40, 0x25},
+ } {
+ trailer := []byte{0xde, 0xad, 0xbe, 0xef}
+ st1.Write(b)
+ st1.Write(trailer)
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ got, err := st2.readVarint()
+ if err != nil {
+ t.Fatalf("st.readVarint() = %v", err)
+ }
+ want, _ := quicwire.ConsumeVarintInt64(b)
+ if got != want {
+ t.Fatalf("st.readVarint() = %v, want %v", got, want)
+ }
+ gotTrailer := make([]byte, len(trailer))
+ if _, err := io.ReadFull(st2, gotTrailer); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(gotTrailer, trailer) {
+ t.Fatalf("after st.readVarint, read %x, want %x", gotTrailer, trailer)
+ }
+ }
+}
+
+func TestStreamWriteVarint(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ for _, v := range []int64{
+ 0,
+ 63,
+ 16383,
+ 1073741823,
+ 4611686018427387903,
+ // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1
+ 151288809941952652,
+ 494878333,
+ 15293,
+ 37,
+ } {
+ trailer := []byte{0xde, 0xad, 0xbe, 0xef}
+ st1.writeVarint(v)
+ st1.Write(trailer)
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ want := quicwire.AppendVarint(nil, uint64(v))
+ want = append(want, trailer...)
+
+ got := make([]byte, len(want))
+ if _, err := io.ReadFull(st2, got); err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(got, want) {
+ t.Errorf("AppendVarint(nil, %v) = %x, want %x", v, got, want)
+ }
+ }
+}
+
+func TestStreamReadFrames(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ for _, frame := range []struct {
+ ftype frameType
+ data []byte
+ }{{
+ ftype: 1,
+ data: []byte("hello"),
+ }, {
+ ftype: 2,
+ data: []byte{},
+ }, {
+ ftype: 3,
+ data: []byte("goodbye"),
+ }} {
+ st1.writeVarint(int64(frame.ftype))
+ st1.writeVarint(int64(len(frame.data)))
+ st1.Write(frame.data)
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if gotFrameType, err := st2.readFrameHeader(); err != nil || gotFrameType != frame.ftype {
+ t.Fatalf("st.readFrameHeader() = %v, %v; want %v, nil", gotFrameType, err, frame.ftype)
+ }
+ if gotData, err := st2.readFrameData(); err != nil || !bytes.Equal(gotData, frame.data) {
+ t.Fatalf("st.readFrameData() = %x, %v; want %x, nil", gotData, err, frame.data)
+ }
+ if err := st2.endFrame(); err != nil {
+ t.Fatalf("st.endFrame() = %v; want nil", err)
+ }
+ }
+}
+
+func TestStreamReadFrameUnderflow(t *testing.T) {
+ const size = 4
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(0) // type
+ st1.writeVarint(size) // size
+ st1.Write(make([]byte, size)) // data
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := st2.readFrameHeader(); err != nil {
+ t.Fatalf("st.readFrameHeader() = %v", err)
+ }
+ if _, err := io.ReadFull(st2, make([]byte, size-1)); err != nil {
+ t.Fatalf("st.Read() = %v", err)
+ }
+ // We have not consumed the full frame: Error.
+ if err := st2.endFrame(); !errors.Is(err, errH3FrameError) {
+ t.Fatalf("st.endFrame before end: %v, want errH3FrameError", err)
+ }
+}
+
+func TestStreamReadFrameWithoutEnd(t *testing.T) {
+ const size = 4
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(0) // type
+ st1.writeVarint(size) // size
+ st1.Write(make([]byte, size)) // data
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := st2.readFrameHeader(); err != nil {
+ t.Fatalf("st.readFrameHeader() = %v", err)
+ }
+ if _, err := st2.readFrameHeader(); err == nil {
+ t.Fatalf("st.readFrameHeader before st.endFrame for prior frame: success, want error")
+ }
+}
+
+func TestStreamReadFrameOverflow(t *testing.T) {
+ const size = 4
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(0) // type
+ st1.writeVarint(size) // size
+ st1.Write(make([]byte, size+1)) // data
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := st2.readFrameHeader(); err != nil {
+ t.Fatalf("st.readFrameHeader() = %v", err)
+ }
+ if _, err := io.ReadFull(st2, make([]byte, size+1)); !errors.Is(err, errH3FrameError) {
+ t.Fatalf("st.Read past end of frame: %v, want errH3FrameError", err)
+ }
+}
+
+func TestStreamReadFrameHeaderPartial(t *testing.T) {
+ var frame []byte
+ frame = quicwire.AppendVarint(frame, 1000) // type
+ frame = quicwire.AppendVarint(frame, 2000) // size
+
+ for i := 1; i < len(frame)-1; i++ {
+ st1, st2 := newStreamPair(t)
+ st1.Write(frame[:i])
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ st1.stream.CloseWrite()
+
+ if _, err := st2.readFrameHeader(); err == nil {
+ t.Fatalf("%v/%v bytes of frame available: st.readFrameHeader() succeded; want error", i, len(frame))
+ }
+ }
+}
+
+func TestStreamReadFrameDataPartial(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(1) // type
+ st1.writeVarint(100) // size
+ st1.Write(make([]byte, 50)) // data
+ st1.stream.CloseWrite()
+ if _, err := st2.readFrameHeader(); err != nil {
+ t.Fatalf("st.readFrameHeader() = %v", err)
+ }
+ if n, err := io.ReadAll(st2); err == nil {
+ t.Fatalf("io.ReadAll with partial frame = %v, nil; want error", n)
+ }
+}
+
+func TestStreamReadByteFrameDataPartial(t *testing.T) {
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(1) // type
+ st1.writeVarint(100) // size
+ st1.stream.CloseWrite()
+ if _, err := st2.readFrameHeader(); err != nil {
+ t.Fatalf("st.readFrameHeader() = %v", err)
+ }
+ if b, err := st2.ReadByte(); err == nil {
+ t.Fatalf("io.ReadAll with partial frame = %v, nil; want error", b)
+ }
+}
+
+func TestStreamReadFrameDataAtEOF(t *testing.T) {
+ const typ = 10
+ data := []byte("hello")
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(typ) // type
+ st1.writeVarint(int64(len(data))) // size
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ if got, err := st2.readFrameHeader(); err != nil || got != typ {
+ t.Fatalf("st.readFrameHeader() = %v, %v; want %v, nil", got, err, typ)
+ }
+
+ st1.Write(data) // data
+ st1.stream.CloseWrite() // end stream
+ got := make([]byte, len(data)+1)
+ if n, err := st2.Read(got); err != nil || n != len(data) || !bytes.Equal(got[:n], data) {
+ t.Fatalf("st.Read() = %v, %v (data=%x); want %v, nil (data=%x)", n, err, got[:n], len(data), data)
+ }
+}
+
+func TestStreamReadFrameData(t *testing.T) {
+ const typ = 10
+ data := []byte("hello")
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(typ) // type
+ st1.writeVarint(int64(len(data))) // size
+ st1.Write(data) // data
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if got, err := st2.readFrameHeader(); err != nil || got != typ {
+ t.Fatalf("st.readFrameHeader() = %v, %v; want %v, nil", got, err, typ)
+ }
+ if got, err := st2.readFrameData(); err != nil || !bytes.Equal(got, data) {
+ t.Fatalf("st.readFrameData() = %x, %v; want %x, nil", got, err, data)
+ }
+}
+
+func TestStreamReadByte(t *testing.T) {
+ const stype = 1
+ const want = 42
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(stype) // stream type
+ st1.writeVarint(1) // size
+ st1.Write([]byte{want}) // data
+ if err := st1.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ if got, err := st2.readFrameHeader(); err != nil || got != stype {
+ t.Fatalf("st.readFrameHeader() = %v, %v; want %v, nil", got, err, stype)
+ }
+ if got, err := st2.ReadByte(); err != nil || got != want {
+ t.Fatalf("st.ReadByte() = %v, %v; want %v, nil", got, err, want)
+ }
+ if got, err := st2.ReadByte(); err == nil {
+ t.Fatalf("reading past end of frame: st.ReadByte() = %v, %v; want error", got, err)
+ }
+}
+
+func TestStreamDiscardFrame(t *testing.T) {
+ const typ = 10
+ data := []byte("hello")
+ st1, st2 := newStreamPair(t)
+ st1.writeVarint(typ) // type
+ st1.writeVarint(int64(len(data))) // size
+ st1.Write(data) // data
+ st1.stream.CloseWrite()
+
+ if got, err := st2.readFrameHeader(); err != nil || got != typ {
+ t.Fatalf("st.readFrameHeader() = %v, %v; want %v, nil", got, err, typ)
+ }
+ if err := st2.discardFrame(); err != nil {
+ t.Fatalf("st.discardFrame() = %v", err)
+ }
+ if b, err := io.ReadAll(st2); err != nil || len(b) > 0 {
+ t.Fatalf("after discarding frame, read %x, %v; want EOF", b, err)
+ }
+}
+
+func newStreamPair(t testing.TB) (s1, s2 *stream) {
+ t.Helper()
+ q1, q2 := newQUICStreamPair(t)
+ return newStream(q1), newStream(q2)
+}
diff --git a/internal/http3/transport.go b/internal/http3/transport.go
new file mode 100644
index 0000000000..b26524cbda
--- /dev/null
+++ b/internal/http3/transport.go
@@ -0,0 +1,190 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.24
+
+package http3
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "golang.org/x/net/quic"
+)
+
+// A Transport is an HTTP/3 transport.
+//
+// It does not manage a pool of connections,
+// and therefore does not implement net/http.RoundTripper.
+//
+// TODO: Provide a way to register an HTTP/3 transport with a net/http.Transport's
+// connection pool.
+type Transport struct {
+ // Endpoint is the QUIC endpoint used by connections created by the transport.
+ // If unset, it is initialized by the first call to Dial.
+ Endpoint *quic.Endpoint
+
+ // Config is the QUIC configuration used for client connections.
+ // The Config may be nil.
+ //
+ // Dial may clone and modify the Config.
+ // The Config must not be modified after calling Dial.
+ Config *quic.Config
+
+ initOnce sync.Once
+ initErr error
+}
+
+func (tr *Transport) init() error {
+ tr.initOnce.Do(func() {
+ tr.Config = initConfig(tr.Config)
+ if tr.Endpoint == nil {
+ tr.Endpoint, tr.initErr = quic.Listen("udp", ":0", nil)
+ }
+ })
+ return tr.initErr
+}
+
+// Dial creates a new HTTP/3 client connection.
+func (tr *Transport) Dial(ctx context.Context, target string) (*ClientConn, error) {
+ if err := tr.init(); err != nil {
+ return nil, err
+ }
+ qconn, err := tr.Endpoint.Dial(ctx, "udp", target, tr.Config)
+ if err != nil {
+ return nil, err
+ }
+ return newClientConn(ctx, qconn)
+}
+
+// A ClientConn is a client HTTP/3 connection.
+//
+// Multiple goroutines may invoke methods on a ClientConn simultaneously.
+type ClientConn struct {
+ qconn *quic.Conn
+ genericConn
+
+ enc qpackEncoder
+ dec qpackDecoder
+}
+
+func newClientConn(ctx context.Context, qconn *quic.Conn) (*ClientConn, error) {
+ cc := &ClientConn{
+ qconn: qconn,
+ }
+ cc.enc.init()
+
+ // Create control stream and send SETTINGS frame.
+ controlStream, err := newConnStream(ctx, cc.qconn, streamTypeControl)
+ if err != nil {
+ return nil, fmt.Errorf("http3: cannot create control stream: %v", err)
+ }
+ controlStream.writeSettings()
+ controlStream.Flush()
+
+ go cc.acceptStreams(qconn, cc)
+ return cc, nil
+}
+
+// Close closes the connection.
+// Any in-flight requests are canceled.
+// Close does not wait for the peer to acknowledge the connection closing.
+func (cc *ClientConn) Close() error {
+ // Close the QUIC connection immediately with a status of NO_ERROR.
+ cc.qconn.Abort(nil)
+
+ // Return any existing error from the peer, but don't wait for it.
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ return cc.qconn.Wait(ctx)
+}
+
+func (cc *ClientConn) handleControlStream(st *stream) error {
+ // "A SETTINGS frame MUST be sent as the first frame of each control stream [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2
+ if err := st.readSettings(func(settingsType, settingsValue int64) error {
+ switch settingsType {
+ case settingsMaxFieldSectionSize:
+ _ = settingsValue // TODO
+ case settingsQPACKMaxTableCapacity:
+ _ = settingsValue // TODO
+ case settingsQPACKBlockedStreams:
+ _ = settingsValue // TODO
+ default:
+ // Unknown settings types are ignored.
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ for {
+ ftype, err := st.readFrameHeader()
+ if err != nil {
+ return err
+ }
+ switch ftype {
+ case frameTypeCancelPush:
+ // "If a CANCEL_PUSH frame is received that references a push ID
+ // greater than currently allowed on the connection,
+ // this MUST be treated as a connection error of type H3_ID_ERROR."
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-7
+ return &connectionError{
+ code: errH3IDError,
+ message: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent",
+ }
+ case frameTypeGoaway:
+ // TODO: Wait for requests to complete before closing connection.
+ return errH3NoError
+ default:
+ // Unknown frames are ignored.
+ if err := st.discardUnknownFrame(ftype); err != nil {
+ return err
+ }
+ }
+ }
+}
+
+func (cc *ClientConn) handleEncoderStream(*stream) error {
+ // TODO
+ return nil
+}
+
+func (cc *ClientConn) handleDecoderStream(*stream) error {
+ // TODO
+ return nil
+}
+
+func (cc *ClientConn) handlePushStream(*stream) error {
+ // "A client MUST treat receipt of a push stream as a connection error
+ // of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.6-3
+ return &connectionError{
+ code: errH3IDError,
+ message: "push stream created when no MAX_PUSH_ID has been sent",
+ }
+}
+
+func (cc *ClientConn) handleRequestStream(st *stream) error {
+ // "Clients MUST treat receipt of a server-initiated bidirectional
+ // stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
+ return &connectionError{
+ code: errH3StreamCreationError,
+ message: "server created bidirectional stream",
+ }
+}
+
+// abort closes the connection with an error.
+func (cc *ClientConn) abort(err error) {
+ if e, ok := err.(*connectionError); ok {
+ cc.qconn.Abort(&quic.ApplicationError{
+ Code: uint64(e.code),
+ Reason: e.message,
+ })
+ } else {
+ cc.qconn.Abort(err)
+ }
+}
diff --git a/internal/http3/transport_test.go b/internal/http3/transport_test.go
new file mode 100644
index 0000000000..b300866390
--- /dev/null
+++ b/internal/http3/transport_test.go
@@ -0,0 +1,448 @@
+// 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.
+
+//go:build go1.24 && goexperiment.synctest
+
+package http3
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "maps"
+ "net/http"
+ "reflect"
+ "slices"
+ "testing"
+ "testing/synctest"
+
+ "golang.org/x/net/internal/quic/quicwire"
+ "golang.org/x/net/quic"
+)
+
+func TestTransportServerCreatesBidirectionalStream(t *testing.T) {
+ // "Clients MUST treat receipt of a server-initiated bidirectional
+ // stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
+ // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
+ runSynctest(t, func(t testing.TB) {
+ tc := newTestClientConn(t)
+ tc.greet()
+ st := tc.newStream(streamTypeRequest)
+ st.Flush()
+ tc.wantClosed("after server creates bidi stream", errH3StreamCreationError)
+ })
+}
+
+// A testQUICConn wraps a *quic.Conn and provides methods for inspecting it.
+type testQUICConn struct {
+ t testing.TB
+ qconn *quic.Conn
+ streams map[streamType][]*testQUICStream
+}
+
+func newTestQUICConn(t testing.TB, qconn *quic.Conn) *testQUICConn {
+ tq := &testQUICConn{
+ t: t,
+ qconn: qconn,
+ streams: make(map[streamType][]*testQUICStream),
+ }
+
+ go tq.acceptStreams(t.Context())
+
+ t.Cleanup(func() {
+ tq.qconn.Close()
+ })
+ return tq
+}
+
+func (tq *testQUICConn) acceptStreams(ctx context.Context) {
+ for {
+ qst, err := tq.qconn.AcceptStream(ctx)
+ if err != nil {
+ return
+ }
+ st := newStream(qst)
+ stype := streamTypeRequest
+ if qst.IsReadOnly() {
+ v, err := st.readVarint()
+ if err != nil {
+ tq.t.Errorf("error reading stream type from unidirectional stream: %v", err)
+ continue
+ }
+ stype = streamType(v)
+ }
+ tq.streams[stype] = append(tq.streams[stype], newTestQUICStream(tq.t, st))
+ }
+}
+
+func (tq *testQUICConn) newStream(stype streamType) *testQUICStream {
+ tq.t.Helper()
+ var qs *quic.Stream
+ var err error
+ if stype == streamTypeRequest {
+ qs, err = tq.qconn.NewStream(canceledCtx)
+ } else {
+ qs, err = tq.qconn.NewSendOnlyStream(canceledCtx)
+ }
+ if err != nil {
+ tq.t.Fatal(err)
+ }
+ st := newStream(qs)
+ if stype != streamTypeRequest {
+ st.writeVarint(int64(stype))
+ if err := st.Flush(); err != nil {
+ tq.t.Fatal(err)
+ }
+ }
+ return newTestQUICStream(tq.t, st)
+}
+
+// wantNotClosed asserts that the peer has not closed the connectioln.
+func (tq *testQUICConn) wantNotClosed(reason string) {
+ t := tq.t
+ t.Helper()
+ synctest.Wait()
+ err := tq.qconn.Wait(canceledCtx)
+ if !errors.Is(err, context.Canceled) {
+ t.Fatalf("%v: want QUIC connection to be alive; closed with error: %v", reason, err)
+ }
+}
+
+// wantClosed asserts that the peer has closed the connection
+// with the provided error code.
+func (tq *testQUICConn) wantClosed(reason string, want error) {
+ t := tq.t
+ t.Helper()
+ synctest.Wait()
+
+ if e, ok := want.(http3Error); ok {
+ want = &quic.ApplicationError{Code: uint64(e)}
+ }
+ got := tq.qconn.Wait(canceledCtx)
+ if errors.Is(got, context.Canceled) {
+ t.Fatalf("%v: want QUIC connection closed, but it is not", reason)
+ }
+ if !errors.Is(got, want) {
+ t.Fatalf("%v: connection closed with error: %v; want %v", reason, got, want)
+ }
+}
+
+// wantStream asserts that a stream of a given type has been created,
+// and returns that stream.
+func (tq *testQUICConn) wantStream(stype streamType) *testQUICStream {
+ tq.t.Helper()
+ synctest.Wait()
+ if len(tq.streams[stype]) == 0 {
+ tq.t.Fatalf("expected a %v stream to be created, but none were", stype)
+ }
+ ts := tq.streams[stype][0]
+ tq.streams[stype] = tq.streams[stype][1:]
+ return ts
+}
+
+// testQUICStream wraps a QUIC stream and provides methods for inspecting it.
+type testQUICStream struct {
+ t testing.TB
+ *stream
+}
+
+func newTestQUICStream(t testing.TB, st *stream) *testQUICStream {
+ st.stream.SetReadContext(canceledCtx)
+ st.stream.SetWriteContext(canceledCtx)
+ return &testQUICStream{
+ t: t,
+ stream: st,
+ }
+}
+
+// wantFrameHeader calls readFrameHeader and asserts that the frame is of a given type.
+func (ts *testQUICStream) wantFrameHeader(reason string, wantType frameType) {
+ ts.t.Helper()
+ synctest.Wait()
+ gotType, err := ts.readFrameHeader()
+ if err != nil {
+ ts.t.Fatalf("%v: failed to read frame header: %v", reason, err)
+ }
+ if gotType != wantType {
+ ts.t.Fatalf("%v: got frame type %v, want %v", reason, gotType, wantType)
+ }
+}
+
+// wantHeaders reads a HEADERS frame.
+// If want is nil, the contents of the frame are ignored.
+func (ts *testQUICStream) wantHeaders(want http.Header) {
+ ts.t.Helper()
+ ftype, err := ts.readFrameHeader()
+ if err != nil {
+ ts.t.Fatalf("want HEADERS frame, got error: %v", err)
+ }
+ if ftype != frameTypeHeaders {
+ ts.t.Fatalf("want HEADERS frame, got: %v", ftype)
+ }
+
+ if want == nil {
+ if err := ts.discardFrame(); err != nil {
+ ts.t.Fatalf("discardFrame: %v", err)
+ }
+ return
+ }
+
+ got := make(http.Header)
+ var dec qpackDecoder
+ err = dec.decode(ts.stream, func(_ indexType, name, value string) error {
+ got.Add(name, value)
+ return nil
+ })
+ if diff := diffHeaders(got, want); diff != "" {
+ ts.t.Fatalf("unexpected response headers:\n%v", diff)
+ }
+ if err := ts.endFrame(); err != nil {
+ ts.t.Fatalf("endFrame: %v", err)
+ }
+}
+
+func (ts *testQUICStream) encodeHeaders(h http.Header) []byte {
+ ts.t.Helper()
+ var enc qpackEncoder
+ return enc.encode(func(yield func(itype indexType, name, value string)) {
+ names := slices.Collect(maps.Keys(h))
+ slices.Sort(names)
+ for _, k := range names {
+ for _, v := range h[k] {
+ yield(mayIndex, k, v)
+ }
+ }
+ })
+}
+
+func (ts *testQUICStream) writeHeaders(h http.Header) {
+ ts.t.Helper()
+ headers := ts.encodeHeaders(h)
+ ts.writeVarint(int64(frameTypeHeaders))
+ ts.writeVarint(int64(len(headers)))
+ ts.Write(headers)
+ if err := ts.Flush(); err != nil {
+ ts.t.Fatalf("flushing HEADERS frame: %v", err)
+ }
+}
+
+func (ts *testQUICStream) wantData(want []byte) {
+ ts.t.Helper()
+ synctest.Wait()
+ ftype, err := ts.readFrameHeader()
+ if err != nil {
+ ts.t.Fatalf("want DATA frame, got error: %v", err)
+ }
+ if ftype != frameTypeData {
+ ts.t.Fatalf("want DATA frame, got: %v", ftype)
+ }
+ got, err := ts.readFrameData()
+ if err != nil {
+ ts.t.Fatalf("error reading DATA frame: %v", err)
+ }
+ if !bytes.Equal(got, want) {
+ ts.t.Fatalf("got data: {%x}, want {%x}", got, want)
+ }
+ if err := ts.endFrame(); err != nil {
+ ts.t.Fatalf("endFrame: %v", err)
+ }
+}
+
+func (ts *testQUICStream) wantClosed(reason string) {
+ ts.t.Helper()
+ synctest.Wait()
+ ftype, err := ts.readFrameHeader()
+ if err != io.EOF {
+ ts.t.Fatalf("%v: want io.EOF, got %v %v", reason, ftype, err)
+ }
+}
+
+func (ts *testQUICStream) wantError(want quic.StreamErrorCode) {
+ ts.t.Helper()
+ synctest.Wait()
+ _, err := ts.stream.stream.ReadByte()
+ if err == nil {
+ ts.t.Fatalf("successfully read from stream; want stream error code %v", want)
+ }
+ var got quic.StreamErrorCode
+ if !errors.As(err, &got) {
+ ts.t.Fatalf("stream error = %v; want %v", err, want)
+ }
+ if got != want {
+ ts.t.Fatalf("stream error code = %v; want %v", got, want)
+ }
+}
+
+func (ts *testQUICStream) writePushPromise(pushID int64, h http.Header) {
+ ts.t.Helper()
+ headers := ts.encodeHeaders(h)
+ ts.writeVarint(int64(frameTypePushPromise))
+ ts.writeVarint(int64(quicwire.SizeVarint(uint64(pushID)) + len(headers)))
+ ts.writeVarint(pushID)
+ ts.Write(headers)
+ if err := ts.Flush(); err != nil {
+ ts.t.Fatalf("flushing PUSH_PROMISE frame: %v", err)
+ }
+}
+
+func diffHeaders(got, want http.Header) string {
+ // nil and 0-length non-nil are equal.
+ if len(got) == 0 && len(want) == 0 {
+ return ""
+ }
+ // We could do a more sophisticated diff here.
+ // DeepEqual is good enough for now.
+ if reflect.DeepEqual(got, want) {
+ return ""
+ }
+ return fmt.Sprintf("got: %v\nwant: %v", got, want)
+}
+
+func (ts *testQUICStream) Flush() error {
+ err := ts.stream.Flush()
+ ts.t.Helper()
+ if err != nil {
+ ts.t.Errorf("unexpected error flushing stream: %v", err)
+ }
+ return err
+}
+
+// A testClientConn is a ClientConn on a test network.
+type testClientConn struct {
+ tr *Transport
+ cc *ClientConn
+
+ // *testQUICConn is the server half of the connection.
+ *testQUICConn
+ control *testQUICStream
+}
+
+func newTestClientConn(t testing.TB) *testClientConn {
+ e1, e2 := newQUICEndpointPair(t)
+ tr := &Transport{
+ Endpoint: e1,
+ Config: &quic.Config{
+ TLSConfig: testTLSConfig,
+ },
+ }
+
+ cc, err := tr.Dial(t.Context(), e2.LocalAddr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ cc.Close()
+ })
+ srvConn, err := e2.Accept(t.Context())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tc := &testClientConn{
+ tr: tr,
+ cc: cc,
+ testQUICConn: newTestQUICConn(t, srvConn),
+ }
+ synctest.Wait()
+ return tc
+}
+
+// greet performs initial connection handshaking with the client.
+func (tc *testClientConn) greet() {
+ // Client creates a control stream.
+ clientControlStream := tc.wantStream(streamTypeControl)
+ clientControlStream.wantFrameHeader(
+ "client sends SETTINGS frame on control stream",
+ frameTypeSettings)
+ clientControlStream.discardFrame()
+
+ // Server creates a control stream.
+ tc.control = tc.newStream(streamTypeControl)
+ tc.control.writeVarint(int64(frameTypeSettings))
+ tc.control.writeVarint(0) // size
+ tc.control.Flush()
+
+ synctest.Wait()
+}
+
+type testRoundTrip struct {
+ t testing.TB
+ resp *http.Response
+ respErr error
+}
+
+func (rt *testRoundTrip) done() bool {
+ synctest.Wait()
+ return rt.resp != nil || rt.respErr != nil
+}
+
+func (rt *testRoundTrip) result() (*http.Response, error) {
+ rt.t.Helper()
+ if !rt.done() {
+ rt.t.Fatal("RoundTrip is not done; want it to be")
+ }
+ return rt.resp, rt.respErr
+}
+
+func (rt *testRoundTrip) response() *http.Response {
+ rt.t.Helper()
+ if !rt.done() {
+ rt.t.Fatal("RoundTrip is not done; want it to be")
+ }
+ if rt.respErr != nil {
+ rt.t.Fatalf("RoundTrip returned unexpected error: %v", rt.respErr)
+ }
+ return rt.resp
+}
+
+// err returns the (possibly nil) error result of RoundTrip.
+func (rt *testRoundTrip) err() error {
+ rt.t.Helper()
+ _, err := rt.result()
+ return err
+}
+
+func (rt *testRoundTrip) wantError(reason string) {
+ rt.t.Helper()
+ synctest.Wait()
+ if !rt.done() {
+ rt.t.Fatalf("%v: RoundTrip is not done; want it to have returned an error", reason)
+ }
+ if rt.respErr == nil {
+ rt.t.Fatalf("%v: RoundTrip succeeded; want it to have returned an error", reason)
+ }
+}
+
+// wantStatus indicates the expected response StatusCode.
+func (rt *testRoundTrip) wantStatus(want int) {
+ rt.t.Helper()
+ if got := rt.response().StatusCode; got != want {
+ rt.t.Fatalf("got response status %v, want %v", got, want)
+ }
+}
+
+func (rt *testRoundTrip) wantHeaders(want http.Header) {
+ rt.t.Helper()
+ if diff := diffHeaders(rt.response().Header, want); diff != "" {
+ rt.t.Fatalf("unexpected response headers:\n%v", diff)
+ }
+}
+
+func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
+ rt := &testRoundTrip{t: tc.t}
+ go func() {
+ rt.resp, rt.respErr = tc.cc.RoundTrip(req)
+ }()
+ return rt
+}
+
+// canceledCtx is a canceled Context.
+// Used for performing non-blocking QUIC operations.
+var canceledCtx = func() context.Context {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ return ctx
+}()
diff --git a/internal/httpcommon/ascii.go b/internal/httpcommon/ascii.go
new file mode 100644
index 0000000000..ed14da5afc
--- /dev/null
+++ b/internal/httpcommon/ascii.go
@@ -0,0 +1,53 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon
+
+import "strings"
+
+// The HTTP protocols are defined in terms of ASCII, not Unicode. This file
+// contains helper functions which may use Unicode-aware functions which would
+// otherwise be unsafe and could introduce vulnerabilities if used improperly.
+
+// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
+// are equal, ASCII-case-insensitively.
+func asciiEqualFold(s, t string) bool {
+ if len(s) != len(t) {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ if lower(s[i]) != lower(t[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// lower returns the ASCII lowercase version of b.
+func lower(b byte) byte {
+ if 'A' <= b && b <= 'Z' {
+ return b + ('a' - 'A')
+ }
+ return b
+}
+
+// isASCIIPrint returns whether s is ASCII and printable according to
+// https://tools.ietf.org/html/rfc20#section-4.2.
+func isASCIIPrint(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] < ' ' || s[i] > '~' {
+ return false
+ }
+ }
+ return true
+}
+
+// asciiToLower returns the lowercase version of s if s is ASCII and printable,
+// and whether or not it was.
+func asciiToLower(s string) (lower string, ok bool) {
+ if !isASCIIPrint(s) {
+ return "", false
+ }
+ return strings.ToLower(s), true
+}
diff --git a/http2/headermap.go b/internal/httpcommon/headermap.go
similarity index 74%
rename from http2/headermap.go
rename to internal/httpcommon/headermap.go
index 149b3dd20e..92483d8e41 100644
--- a/http2/headermap.go
+++ b/internal/httpcommon/headermap.go
@@ -1,11 +1,11 @@
-// Copyright 2014 The Go Authors. All rights reserved.
+// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package http2
+package httpcommon
import (
- "net/http"
+ "net/textproto"
"sync"
)
@@ -82,13 +82,15 @@ func buildCommonHeaderMaps() {
commonLowerHeader = make(map[string]string, len(common))
commonCanonHeader = make(map[string]string, len(common))
for _, v := range common {
- chk := http.CanonicalHeaderKey(v)
+ chk := textproto.CanonicalMIMEHeaderKey(v)
commonLowerHeader[chk] = v
commonCanonHeader[v] = chk
}
}
-func lowerHeader(v string) (lower string, ascii bool) {
+// LowerHeader returns the lowercase form of a header name,
+// used on the wire for HTTP/2 and HTTP/3 requests.
+func LowerHeader(v string) (lower string, ascii bool) {
buildCommonHeaderMapsOnce()
if s, ok := commonLowerHeader[v]; ok {
return s, true
@@ -96,10 +98,18 @@ func lowerHeader(v string) (lower string, ascii bool) {
return asciiToLower(v)
}
-func canonicalHeader(v string) string {
+// CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".)
+func CanonicalHeader(v string) string {
buildCommonHeaderMapsOnce()
if s, ok := commonCanonHeader[v]; ok {
return s
}
- return http.CanonicalHeaderKey(v)
+ return textproto.CanonicalMIMEHeaderKey(v)
+}
+
+// CachedCanonicalHeader returns the canonical form of a well-known header name.
+func CachedCanonicalHeader(v string) (string, bool) {
+ buildCommonHeaderMapsOnce()
+ s, ok := commonCanonHeader[v]
+ return s, ok
}
diff --git a/internal/httpcommon/httpcommon_test.go b/internal/httpcommon/httpcommon_test.go
new file mode 100644
index 0000000000..e725ec76cb
--- /dev/null
+++ b/internal/httpcommon/httpcommon_test.go
@@ -0,0 +1,37 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon_test
+
+import (
+ "bytes"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+// This package is imported by the net/http package,
+// and therefore must not itself import net/http.
+func TestNoNetHttp(t *testing.T) {
+ files, err := filepath.Glob("*.go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, file := range files {
+ if strings.HasSuffix(file, "_test.go") {
+ continue
+ }
+ // Could use something complex like go/build or x/tools/go/packages,
+ // but there's no reason for "net/http" to appear (in quotes) in the source
+ // otherwise, so just use a simple substring search.
+ data, err := os.ReadFile(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if bytes.Contains(data, []byte(`"net/http"`)) {
+ t.Errorf(`%s: cannot import "net/http"`, file)
+ }
+ }
+}
diff --git a/internal/httpcommon/request.go b/internal/httpcommon/request.go
new file mode 100644
index 0000000000..4b70553179
--- /dev/null
+++ b/internal/httpcommon/request.go
@@ -0,0 +1,467 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http/httptrace"
+ "net/textproto"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/http/httpguts"
+ "golang.org/x/net/http2/hpack"
+)
+
+var (
+ ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
+)
+
+// Request is a subset of http.Request.
+// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
+// without creating a dependency cycle.
+type Request struct {
+ URL *url.URL
+ Method string
+ Host string
+ Header map[string][]string
+ Trailer map[string][]string
+ ActualContentLength int64 // 0 means 0, -1 means unknown
+}
+
+// EncodeHeadersParam is parameters to EncodeHeaders.
+type EncodeHeadersParam struct {
+ Request Request
+
+ // AddGzipHeader indicates that an "accept-encoding: gzip" header should be
+ // added to the request.
+ AddGzipHeader bool
+
+ // PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting.
+ PeerMaxHeaderListSize uint64
+
+ // DefaultUserAgent is the User-Agent header to send when the request
+ // neither contains a User-Agent nor disables it.
+ DefaultUserAgent string
+}
+
+// EncodeHeadersParam is the result of EncodeHeaders.
+type EncodeHeadersResult struct {
+ HasBody bool
+ HasTrailers bool
+}
+
+// EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3.
+// It validates a request and calls headerf with each pseudo-header and header
+// for the request.
+// The headerf function is called with the validated, canonicalized header name.
+func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
+ req := param.Request
+
+ // Check for invalid connection-level headers.
+ if err := checkConnHeaders(req.Header); err != nil {
+ return res, err
+ }
+
+ if req.URL == nil {
+ return res, errors.New("Request.URL is nil")
+ }
+
+ host := req.Host
+ if host == "" {
+ host = req.URL.Host
+ }
+ host, err := httpguts.PunycodeHostPort(host)
+ if err != nil {
+ return res, err
+ }
+ if !httpguts.ValidHostHeader(host) {
+ return res, errors.New("invalid Host header")
+ }
+
+ // isNormalConnect is true if this is a non-extended CONNECT request.
+ isNormalConnect := false
+ var protocol string
+ if vv := req.Header[":protocol"]; len(vv) > 0 {
+ protocol = vv[0]
+ }
+ if req.Method == "CONNECT" && protocol == "" {
+ isNormalConnect = true
+ } else if protocol != "" && req.Method != "CONNECT" {
+ return res, errors.New("invalid :protocol header in non-CONNECT request")
+ }
+
+ // Validate the path, except for non-extended CONNECT requests which have no path.
+ var path string
+ if !isNormalConnect {
+ path = req.URL.RequestURI()
+ if !validPseudoPath(path) {
+ orig := path
+ path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
+ if !validPseudoPath(path) {
+ if req.URL.Opaque != "" {
+ return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
+ } else {
+ return res, fmt.Errorf("invalid request :path %q", orig)
+ }
+ }
+ }
+ }
+
+ // Check for any invalid headers+trailers and return an error before we
+ // potentially pollute our hpack state. (We want to be able to
+ // continue to reuse the hpack encoder for future requests)
+ if err := validateHeaders(req.Header); err != "" {
+ return res, fmt.Errorf("invalid HTTP header %s", err)
+ }
+ if err := validateHeaders(req.Trailer); err != "" {
+ return res, fmt.Errorf("invalid HTTP trailer %s", err)
+ }
+
+ trailers, err := commaSeparatedTrailers(req.Trailer)
+ if err != nil {
+ return res, err
+ }
+
+ enumerateHeaders := func(f func(name, value string)) {
+ // 8.1.2.3 Request Pseudo-Header Fields
+ // The :path pseudo-header field includes the path and query parts of the
+ // target URI (the path-absolute production and optionally a '?' character
+ // followed by the query production, see Sections 3.3 and 3.4 of
+ // [RFC3986]).
+ f(":authority", host)
+ m := req.Method
+ if m == "" {
+ m = "GET"
+ }
+ f(":method", m)
+ if !isNormalConnect {
+ f(":path", path)
+ f(":scheme", req.URL.Scheme)
+ }
+ if protocol != "" {
+ f(":protocol", protocol)
+ }
+ if trailers != "" {
+ f("trailer", trailers)
+ }
+
+ var didUA bool
+ for k, vv := range req.Header {
+ if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
+ // Host is :authority, already sent.
+ // Content-Length is automatic, set below.
+ continue
+ } else if asciiEqualFold(k, "connection") ||
+ asciiEqualFold(k, "proxy-connection") ||
+ asciiEqualFold(k, "transfer-encoding") ||
+ asciiEqualFold(k, "upgrade") ||
+ asciiEqualFold(k, "keep-alive") {
+ // Per 8.1.2.2 Connection-Specific Header
+ // Fields, don't send connection-specific
+ // fields. We have already checked if any
+ // are error-worthy so just ignore the rest.
+ continue
+ } else if asciiEqualFold(k, "user-agent") {
+ // Match Go's http1 behavior: at most one
+ // User-Agent. If set to nil or empty string,
+ // then omit it. Otherwise if not mentioned,
+ // include the default (below).
+ didUA = true
+ if len(vv) < 1 {
+ continue
+ }
+ vv = vv[:1]
+ if vv[0] == "" {
+ continue
+ }
+ } else if asciiEqualFold(k, "cookie") {
+ // Per 8.1.2.5 To allow for better compression efficiency, the
+ // Cookie header field MAY be split into separate header fields,
+ // each with one or more cookie-pairs.
+ for _, v := range vv {
+ for {
+ p := strings.IndexByte(v, ';')
+ if p < 0 {
+ break
+ }
+ f("cookie", v[:p])
+ p++
+ // strip space after semicolon if any.
+ for p+1 <= len(v) && v[p] == ' ' {
+ p++
+ }
+ v = v[p:]
+ }
+ if len(v) > 0 {
+ f("cookie", v)
+ }
+ }
+ continue
+ } else if k == ":protocol" {
+ // :protocol pseudo-header was already sent above.
+ continue
+ }
+
+ for _, v := range vv {
+ f(k, v)
+ }
+ }
+ if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
+ f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
+ }
+ if param.AddGzipHeader {
+ f("accept-encoding", "gzip")
+ }
+ if !didUA {
+ f("user-agent", param.DefaultUserAgent)
+ }
+ }
+
+ // Do a first pass over the headers counting bytes to ensure
+ // we don't exceed cc.peerMaxHeaderListSize. This is done as a
+ // separate pass before encoding the headers to prevent
+ // modifying the hpack state.
+ if param.PeerMaxHeaderListSize > 0 {
+ hlSize := uint64(0)
+ enumerateHeaders(func(name, value string) {
+ hf := hpack.HeaderField{Name: name, Value: value}
+ hlSize += uint64(hf.Size())
+ })
+
+ if hlSize > param.PeerMaxHeaderListSize {
+ return res, ErrRequestHeaderListSize
+ }
+ }
+
+ trace := httptrace.ContextClientTrace(ctx)
+
+ // Header list size is ok. Write the headers.
+ enumerateHeaders(func(name, value string) {
+ name, ascii := LowerHeader(name)
+ if !ascii {
+ // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
+ // field names have to be ASCII characters (just as in HTTP/1.x).
+ return
+ }
+
+ headerf(name, value)
+
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField(name, []string{value})
+ }
+ })
+
+ res.HasBody = req.ActualContentLength != 0
+ res.HasTrailers = trailers != ""
+ return res, nil
+}
+
+// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
+// for a request.
+func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
+ // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
+ if !disableCompression &&
+ len(header["Accept-Encoding"]) == 0 &&
+ len(header["Range"]) == 0 &&
+ method != "HEAD" {
+ // Request gzip only, not deflate. Deflate is ambiguous and
+ // not as universally supported anyway.
+ // See: https://zlib.net/zlib_faq.html#faq39
+ //
+ // Note that we don't request this for HEAD requests,
+ // due to a bug in nginx:
+ // http://trac.nginx.org/nginx/ticket/358
+ // https://golang.org/issue/5522
+ //
+ // We don't request gzip if the request is for a range, since
+ // auto-decoding a portion of a gzipped document will just fail
+ // anyway. See https://golang.org/issue/8923
+ return true
+ }
+ return false
+}
+
+// checkConnHeaders checks whether req has any invalid connection-level headers.
+//
+// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3
+// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1
+//
+// Certain headers are special-cased as okay but not transmitted later.
+// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
+func checkConnHeaders(h map[string][]string) error {
+ if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
+ return fmt.Errorf("invalid Upgrade request header: %q", vv)
+ }
+ if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
+ return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
+ }
+ if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
+ return fmt.Errorf("invalid Connection request header: %q", vv)
+ }
+ return nil
+}
+
+func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
+ keys := make([]string, 0, len(trailer))
+ for k := range trailer {
+ k = CanonicalHeader(k)
+ switch k {
+ case "Transfer-Encoding", "Trailer", "Content-Length":
+ return "", fmt.Errorf("invalid Trailer key %q", k)
+ }
+ keys = append(keys, k)
+ }
+ if len(keys) > 0 {
+ sort.Strings(keys)
+ return strings.Join(keys, ","), nil
+ }
+ return "", nil
+}
+
+// validPseudoPath reports whether v is a valid :path pseudo-header
+// value. It must be either:
+//
+// - a non-empty string starting with '/'
+// - the string '*', for OPTIONS requests.
+//
+// For now this is only used a quick check for deciding when to clean
+// up Opaque URLs before sending requests from the Transport.
+// See golang.org/issue/16847
+//
+// We used to enforce that the path also didn't start with "//", but
+// Google's GFE accepts such paths and Chrome sends them, so ignore
+// that part of the spec. See golang.org/issue/19103.
+func validPseudoPath(v string) bool {
+ return (len(v) > 0 && v[0] == '/') || v == "*"
+}
+
+func validateHeaders(hdrs map[string][]string) string {
+ for k, vv := range hdrs {
+ if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
+ return fmt.Sprintf("name %q", k)
+ }
+ for _, v := range vv {
+ if !httpguts.ValidHeaderFieldValue(v) {
+ // Don't include the value in the error,
+ // because it may be sensitive.
+ return fmt.Sprintf("value for header %q", k)
+ }
+ }
+ }
+ return ""
+}
+
+// shouldSendReqContentLength reports whether we should send
+// a "content-length" request header. This logic is basically a copy of the net/http
+// transferWriter.shouldSendContentLength.
+// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
+// -1 means unknown.
+func shouldSendReqContentLength(method string, contentLength int64) bool {
+ if contentLength > 0 {
+ return true
+ }
+ if contentLength < 0 {
+ return false
+ }
+ // For zero bodies, whether we send a content-length depends on the method.
+ // It also kinda doesn't matter for http2 either way, with END_STREAM.
+ switch method {
+ case "POST", "PUT", "PATCH":
+ return true
+ default:
+ return false
+ }
+}
+
+// ServerRequestParam is parameters to NewServerRequest.
+type ServerRequestParam struct {
+ Method string
+ Scheme, Authority, Path string
+ Protocol string
+ Header map[string][]string
+}
+
+// ServerRequestResult is the result of NewServerRequest.
+type ServerRequestResult struct {
+ // Various http.Request fields.
+ URL *url.URL
+ RequestURI string
+ Trailer map[string][]string
+
+ NeedsContinue bool // client provided an "Expect: 100-continue" header
+
+ // If the request should be rejected, this is a short string suitable for passing
+ // to the http2 package's CountError function.
+ // It might be a bit odd to return errors this way rather than returing an error,
+ // but this ensures we don't forget to include a CountError reason.
+ InvalidReason string
+}
+
+func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
+ needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
+ if needsContinue {
+ delete(rp.Header, "Expect")
+ }
+ // Merge Cookie headers into one "; "-delimited value.
+ if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
+ rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
+ }
+
+ // Setup Trailers
+ var trailer map[string][]string
+ for _, v := range rp.Header["Trailer"] {
+ for _, key := range strings.Split(v, ",") {
+ key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
+ switch key {
+ case "Transfer-Encoding", "Trailer", "Content-Length":
+ // Bogus. (copy of http1 rules)
+ // Ignore.
+ default:
+ if trailer == nil {
+ trailer = make(map[string][]string)
+ }
+ trailer[key] = nil
+ }
+ }
+ }
+ delete(rp.Header, "Trailer")
+
+ // "':authority' MUST NOT include the deprecated userinfo subcomponent
+ // for "http" or "https" schemed URIs."
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
+ if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
+ return ServerRequestResult{
+ InvalidReason: "userinfo_in_authority",
+ }
+ }
+
+ var url_ *url.URL
+ var requestURI string
+ if rp.Method == "CONNECT" && rp.Protocol == "" {
+ url_ = &url.URL{Host: rp.Authority}
+ requestURI = rp.Authority // mimic HTTP/1 server behavior
+ } else {
+ var err error
+ url_, err = url.ParseRequestURI(rp.Path)
+ if err != nil {
+ return ServerRequestResult{
+ InvalidReason: "bad_path",
+ }
+ }
+ requestURI = rp.Path
+ }
+
+ return ServerRequestResult{
+ URL: url_,
+ NeedsContinue: needsContinue,
+ RequestURI: requestURI,
+ Trailer: trailer,
+ }
+}
diff --git a/internal/httpcommon/request_test.go b/internal/httpcommon/request_test.go
new file mode 100644
index 0000000000..b8792977c1
--- /dev/null
+++ b/internal/httpcommon/request_test.go
@@ -0,0 +1,672 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httpcommon
+
+import (
+ "cmp"
+ "context"
+ "io"
+ "net/http"
+ "slices"
+ "strings"
+ "testing"
+)
+
+func TestEncodeHeaders(t *testing.T) {
+ type header struct {
+ name string
+ value string
+ }
+ for _, test := range []struct {
+ name string
+ in EncodeHeadersParam
+ want EncodeHeadersResult
+ wantHeaders []header
+ disableCompression bool
+ }{{
+ name: "simple request",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ return must(http.NewRequest("GET", "https://example.tld/", nil))
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "host set from URL",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Host = ""
+ req.URL.Host = "example.tld"
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "chunked transfer-encoding",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Transfer-Encoding", "chunked") // ignored
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "connection close",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Connection", "close")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "connection keep-alive",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Connection", "keep-alive")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "normal connect",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ return must(http.NewRequest("CONNECT", "https://example.tld/", nil))
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "CONNECT"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "extended connect",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("CONNECT", "https://example.tld/", nil))
+ req.Header.Set(":protocol", "foo")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "CONNECT"},
+ {":path", "/"},
+ {":protocol", "foo"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "trailers",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Trailer = make(http.Header)
+ req.Trailer.Set("a", "1")
+ req.Trailer.Set("b", "2")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: true,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"trailer", "A,B"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "override user-agent",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("User-Agent", "GopherTron 9000")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "GopherTron 9000"},
+ },
+ }, {
+ name: "disable user-agent",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header["User-Agent"] = nil
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ },
+ }, {
+ name: "ignore host header",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Host", "gophers.tld/") // ignored
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "crumble cookie header",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Cookie", "a=b; b=c; c=d")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ // Cookie header is split into separate header fields.
+ {"cookie", "a=b"},
+ {"cookie", "b=c"},
+ {"cookie", "c=d"},
+ },
+ }, {
+ name: "post with nil body",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ return must(http.NewRequest("POST", "https://example.tld/", nil))
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "POST"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ {"content-length", "0"},
+ },
+ }, {
+ name: "post with NoBody",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ return must(http.NewRequest("POST", "https://example.tld/", http.NoBody))
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "POST"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ {"content-length", "0"},
+ },
+ }, {
+ name: "post with Content-Length",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ type reader struct{ io.ReadCloser }
+ req := must(http.NewRequest("POST", "https://example.tld/", reader{}))
+ req.ContentLength = 10
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: true,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "POST"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ {"content-length", "10"},
+ },
+ }, {
+ name: "post with unknown Content-Length",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ type reader struct{ io.ReadCloser }
+ req := must(http.NewRequest("POST", "https://example.tld/", reader{}))
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: true,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "POST"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "gzip"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "explicit accept-encoding",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Accept-Encoding", "deflate")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "GET"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"accept-encoding", "deflate"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "head request",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ return must(http.NewRequest("HEAD", "https://example.tld/", nil))
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "HEAD"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"user-agent", "default-user-agent"},
+ },
+ }, {
+ name: "range request",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("HEAD", "https://example.tld/", nil))
+ req.Header.Set("Range", "bytes=0-10")
+ return req
+ }),
+ DefaultUserAgent: "default-user-agent",
+ },
+ want: EncodeHeadersResult{
+ HasBody: false,
+ HasTrailers: false,
+ },
+ wantHeaders: []header{
+ {":authority", "example.tld"},
+ {":method", "HEAD"},
+ {":path", "/"},
+ {":scheme", "https"},
+ {"user-agent", "default-user-agent"},
+ {"range", "bytes=0-10"},
+ },
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ var gotHeaders []header
+ if IsRequestGzip(test.in.Request.Method, test.in.Request.Header, test.disableCompression) {
+ test.in.AddGzipHeader = true
+ }
+
+ got, err := EncodeHeaders(context.Background(), test.in, func(name, value string) {
+ gotHeaders = append(gotHeaders, header{name, value})
+ })
+ if err != nil {
+ t.Fatalf("EncodeHeaders = %v", err)
+ }
+ if got.HasBody != test.want.HasBody {
+ t.Errorf("HasBody = %v, want %v", got.HasBody, test.want.HasBody)
+ }
+ if got.HasTrailers != test.want.HasTrailers {
+ t.Errorf("HasTrailers = %v, want %v", got.HasTrailers, test.want.HasTrailers)
+ }
+ cmpHeader := func(a, b header) int {
+ return cmp.Or(
+ cmp.Compare(a.name, b.name),
+ cmp.Compare(a.value, b.value),
+ )
+ }
+ slices.SortFunc(gotHeaders, cmpHeader)
+ slices.SortFunc(test.wantHeaders, cmpHeader)
+ if !slices.Equal(gotHeaders, test.wantHeaders) {
+ t.Errorf("got headers:")
+ for _, h := range gotHeaders {
+ t.Errorf(" %v: %q", h.name, h.value)
+ }
+ t.Errorf("want headers:")
+ for _, h := range test.wantHeaders {
+ t.Errorf(" %v: %q", h.name, h.value)
+ }
+ }
+ })
+ }
+}
+
+func TestEncodeHeaderErrors(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ in EncodeHeadersParam
+ want string
+ }{{
+ name: "URL is nil",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.URL = nil
+ return req
+ }),
+ },
+ want: "URL is nil",
+ }, {
+ name: "upgrade header is set",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Upgrade", "foo")
+ return req
+ }),
+ },
+ want: "Upgrade",
+ }, {
+ name: "unsupported transfer-encoding header",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Transfer-Encoding", "identity")
+ return req
+ }),
+ },
+ want: "Transfer-Encoding",
+ }, {
+ name: "unsupported connection header",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("Connection", "x")
+ return req
+ }),
+ },
+ want: "Connection",
+ }, {
+ name: "invalid host",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Host = "\x00.tld"
+ return req
+ }),
+ },
+ want: "Host",
+ }, {
+ name: "protocol header is set",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set(":protocol", "foo")
+ return req
+ }),
+ },
+ want: ":protocol",
+ }, {
+ name: "invalid path",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.URL.Path = "no_leading_slash"
+ return req
+ }),
+ },
+ want: "path",
+ }, {
+ name: "invalid header name",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("x\ny", "foo")
+ return req
+ }),
+ },
+ want: "header",
+ }, {
+ name: "invalid header value",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("x", "foo\nbar")
+ return req
+ }),
+ },
+ want: "header",
+ }, {
+ name: "invalid trailer",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Trailer = make(http.Header)
+ req.Trailer.Set("x\ny", "foo")
+ return req
+ }),
+ },
+ want: "trailer",
+ }, {
+ name: "transfer-encoding trailer",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Trailer = make(http.Header)
+ req.Trailer.Set("Transfer-Encoding", "chunked")
+ return req
+ }),
+ },
+ want: "Trailer",
+ }, {
+ name: "trailer trailer",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Trailer = make(http.Header)
+ req.Trailer.Set("Trailer", "chunked")
+ return req
+ }),
+ },
+ want: "Trailer",
+ }, {
+ name: "content-length trailer",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Trailer = make(http.Header)
+ req.Trailer.Set("Content-Length", "0")
+ return req
+ }),
+ },
+ want: "Trailer",
+ }, {
+ name: "too many headers",
+ in: EncodeHeadersParam{
+ Request: newReq(func() *http.Request {
+ req := must(http.NewRequest("GET", "https://example.tld/", nil))
+ req.Header.Set("X-Foo", strings.Repeat("x", 1000))
+ return req
+ }),
+ PeerMaxHeaderListSize: 1000,
+ },
+ want: "limit",
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := EncodeHeaders(context.Background(), test.in, func(name, value string) {})
+ if err == nil {
+ t.Fatalf("EncodeHeaders = nil, want %q", test.want)
+ }
+ if !strings.Contains(err.Error(), test.want) {
+ t.Fatalf("EncodeHeaders = %q, want error containing %q", err, test.want)
+ }
+ })
+ }
+}
+
+func newReq(f func() *http.Request) Request {
+ req := f()
+ contentLength := req.ContentLength
+ if req.Body == nil || req.Body == http.NoBody {
+ contentLength = 0
+ } else if contentLength == 0 {
+ contentLength = -1
+ }
+ return Request{
+ Header: req.Header,
+ Trailer: req.Trailer,
+ URL: req.URL,
+ Host: req.Host,
+ Method: req.Method,
+ ActualContentLength: contentLength,
+ }
+}
+
+func must[T any](v T, err error) T {
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
diff --git a/quic/wire.go b/internal/quic/quicwire/wire.go
similarity index 65%
rename from quic/wire.go
rename to internal/quic/quicwire/wire.go
index 8486029151..0edf42227d 100644
--- a/quic/wire.go
+++ b/internal/quic/quicwire/wire.go
@@ -4,20 +4,22 @@
//go:build go1.21
-package quic
+// Package quicwire encodes and decode QUIC/HTTP3 wire encoding types,
+// particularly variable-length integers.
+package quicwire
import "encoding/binary"
const (
- maxVarintSize = 8 // encoded size in bytes
- maxVarint = (1 << 62) - 1
+ MaxVarintSize = 8 // encoded size in bytes
+ MaxVarint = (1 << 62) - 1
)
-// consumeVarint parses a variable-length integer, reporting its length.
+// ConsumeVarint parses a variable-length integer, reporting its length.
// It returns a negative length upon an error.
//
// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
-func consumeVarint(b []byte) (v uint64, n int) {
+func ConsumeVarint(b []byte) (v uint64, n int) {
if len(b) < 1 {
return 0, -1
}
@@ -44,17 +46,17 @@ func consumeVarint(b []byte) (v uint64, n int) {
return 0, -1
}
-// consumeVarint64 parses a variable-length integer as an int64.
-func consumeVarintInt64(b []byte) (v int64, n int) {
- u, n := consumeVarint(b)
+// consumeVarintInt64 parses a variable-length integer as an int64.
+func ConsumeVarintInt64(b []byte) (v int64, n int) {
+ u, n := ConsumeVarint(b)
// QUIC varints are 62-bits large, so this conversion can never overflow.
return int64(u), n
}
-// appendVarint appends a variable-length integer to b.
+// AppendVarint appends a variable-length integer to b.
//
// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
-func appendVarint(b []byte, v uint64) []byte {
+func AppendVarint(b []byte, v uint64) []byte {
switch {
case v <= 63:
return append(b, byte(v))
@@ -69,8 +71,8 @@ func appendVarint(b []byte, v uint64) []byte {
}
}
-// sizeVarint returns the size of the variable-length integer encoding of f.
-func sizeVarint(v uint64) int {
+// SizeVarint returns the size of the variable-length integer encoding of f.
+func SizeVarint(v uint64) int {
switch {
case v <= 63:
return 1
@@ -85,28 +87,28 @@ func sizeVarint(v uint64) int {
}
}
-// consumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length.
+// ConsumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length.
// It returns a negative length upon an error.
-func consumeUint32(b []byte) (uint32, int) {
+func ConsumeUint32(b []byte) (uint32, int) {
if len(b) < 4 {
return 0, -1
}
return binary.BigEndian.Uint32(b), 4
}
-// consumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length.
+// ConsumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length.
// It returns a negative length upon an error.
-func consumeUint64(b []byte) (uint64, int) {
+func ConsumeUint64(b []byte) (uint64, int) {
if len(b) < 8 {
return 0, -1
}
return binary.BigEndian.Uint64(b), 8
}
-// consumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length,
+// ConsumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length,
// reporting the total number of bytes consumed.
// It returns a negative length upon an error.
-func consumeUint8Bytes(b []byte) ([]byte, int) {
+func ConsumeUint8Bytes(b []byte) ([]byte, int) {
if len(b) < 1 {
return nil, -1
}
@@ -118,8 +120,8 @@ func consumeUint8Bytes(b []byte) ([]byte, int) {
return b[n:][:size], size + n
}
-// appendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length.
-func appendUint8Bytes(b, v []byte) []byte {
+// AppendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length.
+func AppendUint8Bytes(b, v []byte) []byte {
if len(v) > 0xff {
panic("uint8-prefixed bytes too large")
}
@@ -128,11 +130,11 @@ func appendUint8Bytes(b, v []byte) []byte {
return b
}
-// consumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length,
+// ConsumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length,
// reporting the total number of bytes consumed.
// It returns a negative length upon an error.
-func consumeVarintBytes(b []byte) ([]byte, int) {
- size, n := consumeVarint(b)
+func ConsumeVarintBytes(b []byte) ([]byte, int) {
+ size, n := ConsumeVarint(b)
if n < 0 {
return nil, -1
}
@@ -142,9 +144,9 @@ func consumeVarintBytes(b []byte) ([]byte, int) {
return b[n:][:size], int(size) + n
}
-// appendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length.
-func appendVarintBytes(b, v []byte) []byte {
- b = appendVarint(b, uint64(len(v)))
+// AppendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length.
+func AppendVarintBytes(b, v []byte) []byte {
+ b = AppendVarint(b, uint64(len(v)))
b = append(b, v...)
return b
}
diff --git a/quic/wire_test.go b/internal/quic/quicwire/wire_test.go
similarity index 73%
rename from quic/wire_test.go
rename to internal/quic/quicwire/wire_test.go
index 379da0d349..9167a5b72f 100644
--- a/quic/wire_test.go
+++ b/internal/quic/quicwire/wire_test.go
@@ -4,7 +4,7 @@
//go:build go1.21
-package quic
+package quicwire
import (
"bytes"
@@ -32,22 +32,22 @@ func TestConsumeVarint(t *testing.T) {
{[]byte{0x25}, 37, 1},
{[]byte{0x40, 0x25}, 37, 2},
} {
- got, gotLen := consumeVarint(test.b)
+ got, gotLen := ConsumeVarint(test.b)
if got != test.want || gotLen != test.wantLen {
- t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
- got, gotLen = consumeVarint(b)
+ got, gotLen = ConsumeVarint(b)
if got != test.want || gotLen != test.wantLen {
- t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
- got, gotLen = consumeVarint(b)
+ got, gotLen = ConsumeVarint(b)
if got != 0 || gotLen >= 0 {
- t.Errorf("consumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen)
+ t.Errorf("ConsumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen)
}
}
}
@@ -69,11 +69,11 @@ func TestAppendVarint(t *testing.T) {
{15293, []byte{0x7b, 0xbd}},
{37, []byte{0x25}},
} {
- got := appendVarint([]byte{}, test.v)
+ got := AppendVarint([]byte{}, test.v)
if !bytes.Equal(got, test.want) {
t.Errorf("AppendVarint(nil, %v) = %x, want %x", test.v, got, test.want)
}
- if gotLen, wantLen := sizeVarint(test.v), len(got); gotLen != wantLen {
+ if gotLen, wantLen := SizeVarint(test.v), len(got); gotLen != wantLen {
t.Errorf("SizeVarint(%v) = %v, want %v", test.v, gotLen, wantLen)
}
}
@@ -88,8 +88,8 @@ func TestConsumeUint32(t *testing.T) {
{[]byte{0x01, 0x02, 0x03, 0x04}, 0x01020304, 4},
{[]byte{0x01, 0x02, 0x03}, 0, -1},
} {
- if got, n := consumeUint32(test.b); got != test.want || n != test.wantLen {
- t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
+ if got, n := ConsumeUint32(test.b); got != test.want || n != test.wantLen {
+ t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
}
}
}
@@ -103,8 +103,8 @@ func TestConsumeUint64(t *testing.T) {
{[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 0x0102030405060708, 8},
{[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 0, -1},
} {
- if got, n := consumeUint64(test.b); got != test.want || n != test.wantLen {
- t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
+ if got, n := ConsumeUint64(test.b); got != test.want || n != test.wantLen {
+ t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
}
}
}
@@ -120,22 +120,22 @@ func TestConsumeVarintBytes(t *testing.T) {
{[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5},
{[]byte{0x40, 0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 6},
} {
- got, gotLen := consumeVarintBytes(test.b)
+ got, gotLen := ConsumeVarintBytes(test.b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
- t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
- got, gotLen = consumeVarintBytes(b)
+ got, gotLen = ConsumeVarintBytes(b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
- t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
- got, gotLen := consumeVarintBytes(b)
+ got, gotLen := ConsumeVarintBytes(b)
if len(got) > 0 || gotLen > 0 {
- t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
+ t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
@@ -147,9 +147,9 @@ func TestConsumeVarintBytesErrors(t *testing.T) {
{0x01},
{0x40, 0x01},
} {
- got, gotLen := consumeVarintBytes(b)
+ got, gotLen := ConsumeVarintBytes(b)
if len(got) > 0 || gotLen > 0 {
- t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
+ t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
}
@@ -164,22 +164,22 @@ func TestConsumeUint8Bytes(t *testing.T) {
{[]byte{0x01, 0x00}, []byte{0x00}, 2},
{[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5},
} {
- got, gotLen := consumeUint8Bytes(test.b)
+ got, gotLen := ConsumeUint8Bytes(test.b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
- t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
- got, gotLen = consumeUint8Bytes(b)
+ got, gotLen = ConsumeUint8Bytes(b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
- t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
+ t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
- got, gotLen := consumeUint8Bytes(b)
+ got, gotLen := ConsumeUint8Bytes(b)
if len(got) > 0 || gotLen > 0 {
- t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
+ t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
@@ -191,35 +191,35 @@ func TestConsumeUint8BytesErrors(t *testing.T) {
{0x01},
{0x04, 0x01, 0x02, 0x03},
} {
- got, gotLen := consumeUint8Bytes(b)
+ got, gotLen := ConsumeUint8Bytes(b)
if len(got) > 0 || gotLen > 0 {
- t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
+ t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
}
func TestAppendUint8Bytes(t *testing.T) {
var got []byte
- got = appendUint8Bytes(got, []byte{})
- got = appendUint8Bytes(got, []byte{0xaa, 0xbb})
+ got = AppendUint8Bytes(got, []byte{})
+ got = AppendUint8Bytes(got, []byte{0xaa, 0xbb})
want := []byte{
0x00,
0x02, 0xaa, 0xbb,
}
if !bytes.Equal(got, want) {
- t.Errorf("appendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want)
+ t.Errorf("AppendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want)
}
}
func TestAppendVarintBytes(t *testing.T) {
var got []byte
- got = appendVarintBytes(got, []byte{})
- got = appendVarintBytes(got, []byte{0xaa, 0xbb})
+ got = AppendVarintBytes(got, []byte{})
+ got = AppendVarintBytes(got, []byte{0xaa, 0xbb})
want := []byte{
0x00,
0x02, 0xaa, 0xbb,
}
if !bytes.Equal(got, want) {
- t.Errorf("appendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want)
+ t.Errorf("AppendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want)
}
}
diff --git a/internal/socket/socket_test.go b/internal/socket/socket_test.go
index 44c196b014..26077a7a5b 100644
--- a/internal/socket/socket_test.go
+++ b/internal/socket/socket_test.go
@@ -445,11 +445,7 @@ func main() {
if runtime.Compiler == "gccgo" {
t.Skip("skipping race test when built with gccgo")
}
- dir, err := os.MkdirTemp("", "testrace")
- if err != nil {
- t.Fatalf("failed to create temp directory: %v", err)
- }
- defer os.RemoveAll(dir)
+ dir := t.TempDir()
goBinary := filepath.Join(runtime.GOROOT(), "bin", "go")
t.Logf("%s version", goBinary)
got, err := exec.Command(goBinary, "version").CombinedOutput()
diff --git a/internal/testcert/testcert.go b/internal/testcert/testcert.go
new file mode 100644
index 0000000000..4d8ae33bba
--- /dev/null
+++ b/internal/testcert/testcert.go
@@ -0,0 +1,36 @@
+// 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 testcert contains a test-only localhost certificate.
+package testcert
+
+import (
+ "strings"
+)
+
+// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
+// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
+// generated from src/crypto/tls:
+// go run generate_cert.go --ecdsa-curve P256 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
+var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
+MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO
+BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa
+MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh
+WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms
+PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK
+BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC
+Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
+AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40
+HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE
+-----END CERTIFICATE-----`)
+
+// LocalhostKey is the private key for localhostCert.
+var LocalhostKey = []byte(testingKey(`-----BEGIN TESTING KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs
+rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd
+hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9
+-----END TESTING KEY-----`))
+
+// testingKey helps keep security scanners from getting excited about a private key in this file.
+func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
diff --git a/proxy/per_host.go b/proxy/per_host.go
index d7d4b8b6e3..32bdf435ec 100644
--- a/proxy/per_host.go
+++ b/proxy/per_host.go
@@ -7,6 +7,7 @@ package proxy
import (
"context"
"net"
+ "net/netip"
"strings"
)
@@ -57,7 +58,8 @@ func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net.
}
func (p *PerHost) dialerForRequest(host string) Dialer {
- if ip := net.ParseIP(host); ip != nil {
+ if nip, err := netip.ParseAddr(host); err == nil {
+ ip := net.IP(nip.AsSlice())
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
@@ -108,8 +110,8 @@ func (p *PerHost) AddFromString(s string) {
}
continue
}
- if ip := net.ParseIP(host); ip != nil {
- p.AddIP(ip)
+ if nip, err := netip.ParseAddr(host); err == nil {
+ p.AddIP(net.IP(nip.AsSlice()))
continue
}
if strings.HasPrefix(host, "*.") {
diff --git a/proxy/per_host_test.go b/proxy/per_host_test.go
index 0447eb427a..b7bcec8ae3 100644
--- a/proxy/per_host_test.go
+++ b/proxy/per_host_test.go
@@ -7,8 +7,9 @@ package proxy
import (
"context"
"errors"
+ "fmt"
"net"
- "reflect"
+ "slices"
"testing"
)
@@ -22,55 +23,118 @@ func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) {
}
func TestPerHost(t *testing.T) {
- expectedDef := []string{
- "example.com:123",
- "1.2.3.4:123",
- "[1001::]:123",
- }
- expectedBypass := []string{
- "localhost:123",
- "zone:123",
- "foo.zone:123",
- "127.0.0.1:123",
- "10.1.2.3:123",
- "[1000::]:123",
- }
-
- t.Run("Dial", func(t *testing.T) {
- var def, bypass recordingProxy
- perHost := NewPerHost(&def, &bypass)
- perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16")
- for _, addr := range expectedDef {
- perHost.Dial("tcp", addr)
+ for _, test := range []struct {
+ config string // passed to PerHost.AddFromString
+ nomatch []string // addrs using the default dialer
+ match []string // addrs using the bypass dialer
+ }{{
+ config: "localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16",
+ nomatch: []string{
+ "example.com:123",
+ "1.2.3.4:123",
+ "[1001::]:123",
+ },
+ match: []string{
+ "localhost:123",
+ "zone:123",
+ "foo.zone:123",
+ "127.0.0.1:123",
+ "10.1.2.3:123",
+ "[1000::]:123",
+ "[1000::%25.example.com]:123",
+ },
+ }, {
+ config: "localhost",
+ nomatch: []string{
+ "127.0.0.1:80",
+ },
+ match: []string{
+ "localhost:80",
+ },
+ }, {
+ config: "*.zone",
+ nomatch: []string{
+ "foo.com:80",
+ },
+ match: []string{
+ "foo.zone:80",
+ "foo.bar.zone:80",
+ },
+ }, {
+ config: "1.2.3.4",
+ nomatch: []string{
+ "127.0.0.1:80",
+ "11.2.3.4:80",
+ },
+ match: []string{
+ "1.2.3.4:80",
+ },
+ }, {
+ config: "10.0.0.0/24",
+ nomatch: []string{
+ "10.0.1.1:80",
+ },
+ match: []string{
+ "10.0.0.1:80",
+ "10.0.0.255:80",
+ },
+ }, {
+ config: "fe80::/10",
+ nomatch: []string{
+ "[fec0::1]:80",
+ "[fec0::1%en0]:80",
+ },
+ match: []string{
+ "[fe80::1]:80",
+ "[fe80::1%en0]:80",
+ },
+ }, {
+ // We don't allow zone IDs in network prefixes,
+ // so this config matches nothing.
+ config: "fe80::%en0/10",
+ nomatch: []string{
+ "[fec0::1]:80",
+ "[fec0::1%en0]:80",
+ "[fe80::1]:80",
+ "[fe80::1%en0]:80",
+ "[fe80::1%en1]:80",
+ },
+ }} {
+ for _, addr := range test.match {
+ testPerHost(t, test.config, addr, true)
}
- for _, addr := range expectedBypass {
- perHost.Dial("tcp", addr)
+ for _, addr := range test.nomatch {
+ testPerHost(t, test.config, addr, false)
}
+ }
+}
- if !reflect.DeepEqual(expectedDef, def.addrs) {
- t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef)
- }
- if !reflect.DeepEqual(expectedBypass, bypass.addrs) {
- t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass)
- }
- })
+func testPerHost(t *testing.T, config, addr string, wantMatch bool) {
+ name := fmt.Sprintf("config %q, dial %q", config, addr)
- t.Run("DialContext", func(t *testing.T) {
- var def, bypass recordingProxy
- perHost := NewPerHost(&def, &bypass)
- perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16")
- for _, addr := range expectedDef {
- perHost.DialContext(context.Background(), "tcp", addr)
- }
- for _, addr := range expectedBypass {
- perHost.DialContext(context.Background(), "tcp", addr)
- }
+ var def, bypass recordingProxy
+ perHost := NewPerHost(&def, &bypass)
+ perHost.AddFromString(config)
+ perHost.Dial("tcp", addr)
- if !reflect.DeepEqual(expectedDef, def.addrs) {
- t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef)
- }
- if !reflect.DeepEqual(expectedBypass, bypass.addrs) {
- t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass)
- }
- })
+ // Dial and DialContext should have the same results.
+ var defc, bypassc recordingProxy
+ perHostc := NewPerHost(&defc, &bypassc)
+ perHostc.AddFromString(config)
+ perHostc.DialContext(context.Background(), "tcp", addr)
+ if !slices.Equal(def.addrs, defc.addrs) {
+ t.Errorf("%v: Dial default=%v, bypass=%v; DialContext default=%v, bypass=%v", name, def.addrs, bypass.addrs, defc.addrs, bypass.addrs)
+ return
+ }
+
+ if got, want := slices.Concat(def.addrs, bypass.addrs), []string{addr}; !slices.Equal(got, want) {
+ t.Errorf("%v: dialed %q, want %q", name, got, want)
+ return
+ }
+
+ gotMatch := len(bypass.addrs) > 0
+ if gotMatch != wantMatch {
+ t.Errorf("%v: matched=%v, want %v", name, gotMatch, wantMatch)
+ return
+ }
}
diff --git a/publicsuffix/data/children b/publicsuffix/data/children
index 08261bffd1..986a246a6c 100644
Binary files a/publicsuffix/data/children and b/publicsuffix/data/children differ
diff --git a/publicsuffix/data/nodes b/publicsuffix/data/nodes
index 1dae6ede8f..38b8999600 100644
Binary files a/publicsuffix/data/nodes and b/publicsuffix/data/nodes differ
diff --git a/publicsuffix/data/text b/publicsuffix/data/text
index 7e516413f6..b151d97de2 100644
--- a/publicsuffix/data/text
+++ b/publicsuffix/data/text
@@ -1 +1 @@
-birkenesoddtangentinglogoweirbitbucketrzynishikatakayamatta-varjjatjomembersaltdalovepopartysfjordiskussionsbereichatinhlfanishikatsuragitappassenger-associationishikawazukamiokameokamakurazakitaurayasudabitternidisrechtrainingloomy-routerbjarkoybjerkreimdbalsan-suedtirololitapunkapsienamsskoganeibmdeveloperauniteroirmemorialombardiadempresashibetsukumiyamagasakinderoyonagunicloudevelopmentaxiijimarriottayninhaccanthobby-siteval-d-aosta-valleyoriikaracolognebinatsukigataiwanumatajimidsundgcahcesuolocustomer-ocimperiautoscanalytics-gatewayonagoyaveroykenflfanpachihayaakasakawaiishopitsitemasekd1kappenginedre-eikerimo-siemenscaledekaascolipicenoboribetsucks3-eu-west-3utilities-16-balestrandabergentappsseekloges3-eu-west-123paginawebcamauction-acornfshostrodawaraktyubinskaunicommbank123kotisivultrobjectselinogradimo-i-rana4u2-localhostrolekanieruchomoscientistordal-o-g-i-nikolaevents3-ap-northeast-2-ddnsking123homepagefrontappchizip61123saitamakawababia-goracleaningheannakadomarineat-urlimanowarudakuneustarostwodzislawdev-myqnapcloudcontrolledgesuite-stagingdyniamusementdllclstagehirnikonantomobelementorayokosukanoyakumoliserniaurland-4-salernord-aurdalipaywhirlimiteddnslivelanddnss3-ap-south-123siteweberlevagangaviikanonji234lima-cityeats3-ap-southeast-123webseiteambulancechireadmyblogspotaribeiraogakicks-assurfakefurniturealmpmninoheguribigawaurskog-holandinggfarsundds3-ap-southeast-20001wwwedeployokote123hjemmesidealerdalaheadjuegoshikibichuobiraustevollimombetsupplyokoze164-balena-devices3-ca-central-123websiteleaf-south-12hparliamentatsunobninsk8s3-eu-central-1337bjugnishimerablackfridaynightjxn--11b4c3ditchyouripatriabloombergretaijindustriesteinkjerbloxcmsaludivtasvuodnakaiwanairlinekobayashimodatecnologiablushakotanishinomiyashironomniwebview-assetsalvadorbmoattachmentsamegawabmsamnangerbmwellbeingzonebnrweatherchannelsdvrdnsamparalleluxenishinoomotegotsukishiwadavvenjargamvikarpaczest-a-la-maisondre-landivttasvuotnakamai-stagingloppennebomlocalzonebonavstackartuzybondigitaloceanspacesamsclubartowest1-usamsunglugsmall-webspacebookonlineboomlaakesvuemielecceboschristmasakilatiron-riopretoeidsvollovesickaruizawabostik-serverrankoshigayachtsandvikcoromantovalle-d-aostakinouebostonakijinsekikogentlentapisa-geekarumaifmemsetkmaxxn--12c1fe0bradescotksatmpaviancapitalonebouncemerckmsdscloudiybounty-fullensakerrypropertiesangovtoyosatoyokawaboutiquebecologialaichaugiangmbhartiengiangminakamichiharaboutireservdrangedalpusercontentoyotapfizerboyfriendoftheinternetflixn--12cfi8ixb8lublindesnesanjosoyrovnoticiasannanishinoshimattelemarkasaokamikitayamatsurinfinitigopocznore-og-uvdalucaniabozen-sudtiroluccanva-appstmnishiokoppegardray-dnsupdaterbozen-suedtirolukowesteuropencraftoyotomiyazakinsurealtypeformesswithdnsannohekinanporovigonohejinternationaluroybplacedogawarabikomaezakirunordkappgfoggiabrandrayddns5ybrasiliadboxoslockerbresciaogashimadachicappadovaapstemp-dnswatchest-mon-blogueurodirumagazinebrindisiciliabroadwaybroke-itvedestrandraydnsanokashibatakashimashikiyosatokigawabrokerbrothermesserlifestylebtimnetzpisdnpharmaciensantamariakebrowsersafetymarketingmodumetacentrumeteorappharmacymruovatlassian-dev-builderschaefflerbrumunddalutskashiharabrusselsantoandreclaimsanukintlon-2bryanskiptveterinaireadthedocsaobernardovre-eikerbrynebwestus2bzhitomirbzzwhitesnowflakecommunity-prochowicecomodalenissandoycompanyaarphdfcbankasumigaurawa-mazowszexn--1ck2e1bambinagisobetsuldalpha-myqnapcloudaccess3-us-east-2ixboxeroxfinityolasiteastus2comparemarkerryhotelsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacomsecaasnesoddeno-stagingrondarcondoshifteditorxn--1ctwolominamatarnobrzegrongrossetouchijiwadedyn-berlincolnissayokoshibahikariyaltakazakinzais-a-bookkeepermarshallstatebankasuyalibabahccavuotnagaraholtaleniwaizumiotsurugashimaintenanceomutazasavonarviikaminoyamaxunispaceconferenceconstructionflashdrivefsncf-ipfsaxoconsuladobeio-static-accesscamdvrcampaniaconsultantranoyconsultingroundhandlingroznysaitohnoshookuwanakayamangyshlakdnepropetrovskanlandyndns-freeboxostrowwlkpmgrphilipsyno-dschokokekscholarshipschoolbusinessebycontactivetrailcontagematsubaravendbambleborkdalvdalcest-le-patron-rancherkasydneyukuhashimokawavoues3-sa-east-1contractorskenissedalcookingruecoolblogdnsfor-better-thanhhoarairforcentralus-1cooperativano-frankivskodjeephonefosschoolsztynsetransiphotographysiocoproductionschulplattforminamiechizenisshingucciprianiigatairaumalatvuopmicrolightinguidefinimaringatlancastercorsicafjschulservercosenzakopanecosidnshome-webservercellikescandypopensocialcouchpotatofrieschwarzgwangjuh-ohtawaramotoineppueblockbusternopilawacouncilcouponscrapper-sitecozoravennaharimalborkaszubytemarketscrappinguitarscrysecretrosnubananarepublic-inquiryurihonjoyenthickaragandaxarnetbankanzakiwielunnerepairbusanagochigasakishimabarakawaharaolbia-tempio-olbiatempioolbialowiezachpomorskiengiangjesdalolipopmcdirepbodyn53cqcxn--1lqs03niyodogawacrankyotobetsumidaknongujaratmallcrdyndns-homednscwhminamifuranocreditcardyndns-iphutholdingservehttpbincheonl-ams-1creditunionionjukujitawaravpagecremonashorokanaiecrewhoswholidaycricketnedalcrimeast-kazakhstanangercrotonecrowniphuyencrsvp4cruiseservehumourcuisinellair-traffic-controllagdenesnaaseinet-freakserveircasertainaircraftingvolloansnasaarlanduponthewifidelitypedreamhostersaotomeldaluxurycuneocupcakecuritibacgiangiangryggeecurvalled-aostargets-itranslatedyndns-mailcutegirlfriendyndns-office-on-the-webhoptogurafedoraprojectransurlfeirafembetsukuis-a-bruinsfanfermodenakasatsunairportrapaniizaferraraferraris-a-bulls-fanferrerotikagoshimalopolskanittedalfetsundyndns-wikimobetsumitakagildeskaliszkolamericanfamilydservemp3fgunmaniwamannorth-kazakhstanfhvalerfilegear-augustowiiheyakagefilegear-deatnuniversitysvardofilegear-gbizfilegear-iefilegear-jpmorgangwonporterfilegear-sg-1filminamiizukamiminefinalchikugokasellfyis-a-candidatefinancefinnoyfirebaseappiemontefirenetlifylkesbiblackbaudcdn-edgestackhero-networkinggroupowiathletajimabaria-vungtaudiopsysharpigboatshawilliamhillfirenzefirestonefireweblikes-piedmontravelersinsurancefirmdalegalleryfishingoldpoint2thisamitsukefitjarfitnessettsurugiminamimakis-a-catererfjalerfkatsushikabeebyteappilottonsberguovdageaidnunjargausdalflekkefjordyndns-workservep2phxn--1lqs71dyndns-remotewdyndns-picserveminecraftransporteflesbergushikamifuranorthflankatsuyamashikokuchuoflickragerokunohealthcareershellflierneflirfloginlinefloppythonanywherealtorfloraflorencefloripalmasfjordenfloristanohatajiris-a-celticsfanfloromskogxn--2m4a15eflowershimokitayamafltravinhlonganflynnhosting-clusterfncashgabadaddjabbottoyourafndyndns1fnwkzfolldalfoolfor-ourfor-somegurownproviderfor-theaterfordebianforexrotheworkpccwinbar0emmafann-arborlandd-dnsiskinkyowariasahikawarszawashtenawsmppl-wawsglobalacceleratorahimeshimakanegasakievennodebalancern4t3l3p0rtatarantours3-ap-northeast-123minsidaarborteaches-yogano-ipifony-123miwebaccelastx4432-b-datacenterprisesakijobservableusercontentateshinanomachintaifun-dnsdojournalistoloseyouriparisor-fronavuotnarashinoharaetnabudejjunipereggio-emilia-romagnaroyboltateyamajureggiocalabriakrehamnayoro0o0forgotdnshimonitayanagithubpreviewsaikisarazure-mobileirfjordynnservepicservequakeforli-cesena-forlicesenaforlillehammerfeste-ipimientaketomisatoolshimonosekikawaforsalegoismailillesandefjordynservebbservesarcasmileforsandasuolodingenfortalfortefosneshimosuwalkis-a-chefashionstorebaseljordyndns-serverisignfotrdynulvikatowicefoxn--2scrj9casinordlandurbanamexnetgamersapporomurafozfr-1fr-par-1fr-par-2franamizuhoboleslawiecommerce-shoppingyeongnamdinhachijohanamakisofukushimaoris-a-conservativegarsheiheijis-a-cparachutingfredrikstadynv6freedesktopazimuthaibinhphuocelotenkawakayamagnetcieszynh-servebeero-stageiseiroumugifuchungbukharag-cloud-championshiphoplixn--30rr7yfreemyiphosteurovisionredumbrellangevagrigentobishimadridvagsoygardenebakkeshibechambagricoharugbydgoszczecin-berlindasdaburfreesitefreetlshimotsukefreisennankokubunjis-a-cubicle-slavellinodeobjectshimotsumafrenchkisshikindleikangerfreseniushinichinanfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganshinjotelulubin-vpncateringebunkyonanaoshimamateramockashiwarafrognfrolandynvpnpluservicesevastopolitiendafrom-akamaized-stagingfrom-alfrom-arfrom-azurewebsiteshikagamiishibuyabukihokuizumobaragusabaerobaticketshinjukuleuvenicefrom-campobassociatest-iserveblogsytenrissadistdlibestadultrentin-sudtirolfrom-coachaseljeducationcillahppiacenzaganfrom-ctrentin-sued-tirolfrom-dcatfooddagestangefrom-decagliarikuzentakataikillfrom-flapymntrentin-suedtirolfrom-gap-east-1from-higashiagatsumagoianiafrom-iafrom-idyroyrvikingulenfrom-ilfrom-in-the-bandairtelebitbridgestonemurorangecloudplatform0from-kshinkamigototalfrom-kyfrom-langsonyantakahamalselveruminamiminowafrom-malvikaufentigerfrom-mdfrom-mein-vigorlicefrom-mifunefrom-mnfrom-modshinshinotsurgeryfrom-mshinshirofrom-mtnfrom-ncatholicurus-4from-ndfrom-nefrom-nhs-heilbronnoysundfrom-njshintokushimafrom-nminamioguni5from-nvalledaostargithubusercontentrentino-a-adigefrom-nycaxiaskvollpagesardegnarutolgaulardalvivanovoldafrom-ohdancefrom-okegawassamukawataris-a-democratrentino-aadigefrom-orfrom-panasonichernovtsykkylvenneslaskerrylogisticsardiniafrom-pratohmamurogawatsonrenderfrom-ris-a-designerimarugame-hostyhostingfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--32vp30hachinoheavyfrom-utsiracusagaeroclubmedecin-addrammenuorodoyerfrom-val-daostavalleyfrom-vtrentino-alto-adigefrom-wafrom-wiardwebthingsjcbnpparibashkiriafrom-wvallee-aosteroyfrom-wyfrosinonefrostabackplaneapplebesbyengerdalp1froyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairtrafficplexus-2fujinomiyadapliefujiokazakinkobearalvahkikonaibetsubame-south-1fujisatoshoeshintomikasaharafujisawafujishiroishidakabiratoridediboxn--3bst00minamisanrikubetsupportrentino-altoadigefujitsuruokakamigaharafujiyoshidappnodearthainguyenfukayabeardubaikawagoefukuchiyamadatsunanjoburgfukudomigawafukuis-a-doctorfukumitsubishigakirkeneshinyoshitomiokamisatokamachippubetsuikitchenfukuokakegawafukuroishikariwakunigamigrationfukusakirovogradoyfukuyamagatakaharunusualpersonfunabashiriuchinadattorelayfunagatakahashimamakiryuohkurafunahashikamiamakusatsumasendaisenergyeongginowaniihamatamakinoharafundfunkfeuerfuoiskujukuriyamandalfuosskoczowindowskrakowinefurubirafurudonordreisa-hockeynutwentertainmentrentino-s-tirolfurukawajimangolffanshiojirishirifujiedafusoctrangfussagamiharafutabayamaguchinomihachimanagementrentino-stirolfutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinais-a-financialadvisor-aurdalfuturecmshioyamelhushirahamatonbetsurnadalfuturehostingfuturemailingfvghakuis-a-gurunzenhakusandnessjoenhaldenhalfmoonscalebookinghostedpictetrentino-sud-tirolhalsakakinokiaham-radio-opinbar1hamburghammarfeastasiahamurakamigoris-a-hard-workershiraokamisunagawahanamigawahanawahandavvesiidanangodaddyn-o-saurealestatefarmerseinehandcrafteducatorprojectrentino-sudtirolhangglidinghangoutrentino-sued-tirolhannannestadhannosegawahanoipinkazohanyuzenhappouzshiratakahagianghasamap-northeast-3hasaminami-alpshishikuis-a-hunterhashbanghasudazaifudaigodogadobeioruntimedio-campidano-mediocampidanomediohasura-appinokokamikoaniikappudopaashisogndalhasvikazteleportrentino-suedtirolhatogayahoooshikamagayaitakamoriokakudamatsuehatoyamazakitahiroshimarcheapartmentshisuifuettertdasnetzhatsukaichikaiseiyoichipshitaramahattfjelldalhayashimamotobusells-for-lesshizukuishimoichilloutsystemscloudsitehazuminobushibukawahelplfinancialhelsinkitakamiizumisanofidonnakamurataitogliattinnhemneshizuokamitondabayashiogamagoriziahemsedalhepforgeblockshoujis-a-knightpointtokaizukamaishikshacknetrentinoa-adigehetemlbfanhigashichichibuzentsujiiehigashihiroshimanehigashiizumozakitakatakanabeautychyattorneyagawakkanaioirasebastopoleangaviikadenagahamaroyhigashikagawahigashikagurasoedahigashikawakitaaikitakyushunantankazunovecorebungoonow-dnshowahigashikurumeinforumzhigashimatsushimarnardalhigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshowtimeloyhigashinarusells-for-uhigashinehigashiomitamanoshiroomghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitamihamadahigashitsunospamproxyhigashiurausukitamotosunnydayhigashiyamatokoriyamanashiibaclieu-1higashiyodogawahigashiyoshinogaris-a-landscaperspectakasakitanakagusukumoldeliveryhippyhiraizumisatohokkaidontexistmein-iservschulecznakaniikawatanagurahirakatashinagawahiranais-a-lawyerhirarahiratsukaeruhirayaizuwakamatsubushikusakadogawahitachiomiyaginozawaonsensiositehitachiotaketakaokalmykiahitraeumtgeradegreehjartdalhjelmelandholyhomegoodshwinnersiiitesilkddiamondsimple-urlhomeipioneerhomelinkyard-cloudjiffyresdalhomelinuxn--3ds443ghomeofficehomesecuritymacaparecidahomesecuritypchiryukyuragiizehomesenseeringhomeskleppippugliahomeunixn--3e0b707ehondahonjyoitakarazukaluganskfh-muensterhornindalhorsells-itrentinoaadigehortendofinternet-dnsimplesitehospitalhotelwithflightsirdalhotmailhoyangerhoylandetakasagooglecodespotrentinoalto-adigehungyenhurdalhurumajis-a-liberalhyllestadhyogoris-a-libertarianhyugawarahyundaiwafuneis-very-evillasalleitungsenis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandoomdnstraceisk01isk02jenv-arubacninhbinhdinhktistoryjeonnamegawajetztrentinostiroljevnakerjewelryjgorajlljls-sto1jls-sto2jls-sto3jmpixolinodeusercontentrentinosud-tiroljnjcloud-ver-jpchitosetogitsuliguriajoyokaichibahcavuotnagaivuotnagaokakyotambabymilk3jozis-a-musicianjpnjprsolarvikhersonlanxessolundbeckhmelnitskiyamasoykosaigawakosakaerodromegalloabatobamaceratachikawafaicloudineencoreapigeekoseis-a-painterhostsolutionslupskhakassiakosheroykoshimizumakis-a-patsfankoshughesomakosugekotohiradomainstitutekotourakouhokumakogenkounosupersalevangerkouyamasudakouzushimatrixn--3pxu8khplaystation-cloudyclusterkozagawakozakis-a-personaltrainerkozowiosomnarviklabudhabikinokawachinaganoharamcocottekpnkppspbarcelonagawakepnord-odalwaysdatabaseballangenkainanaejrietisalatinabenogiehtavuoatnaamesjevuemielnombrendlyngen-rootaruibxos3-us-gov-west-1krasnikahokutokonamegatakatoris-a-photographerokussldkrasnodarkredstonekrelliankristiansandcatsoowitdkmpspawnextdirectrentinosudtirolkristiansundkrodsheradkrokstadelvaldaostavangerkropyvnytskyis-a-playershiftcryptonomichinomiyakekryminamiyamashirokawanabelaudnedalnkumamotoyamatsumaebashimofusakatakatsukis-a-republicanonoichinosekigaharakumanowtvaokumatorinokumejimatsumotofukekumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-rockstarachowicekunitachiaraisaijolsterkunitomigusukukis-a-socialistgstagekunneppubtlsopotrentinosued-tirolkuokgroupizzakurgankurobegetmyipirangalluplidlugolekagaminorddalkurogimimozaokinawashirosatochiokinoshimagentositempurlkuroisodegaurakuromatsunais-a-soxfankuronkurotakikawasakis-a-studentalkushirogawakustanais-a-teacherkassyncloudkusuppliesor-odalkutchanelkutnokuzumakis-a-techietipslzkvafjordkvalsundkvamsterdamnserverbaniakvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsor-varangermishimatsusakahogirlymisugitokorozawamitakeharamitourismartlabelingmitoyoakemiuramiyazurecontainerdpoliticaobangmiyotamatsukuris-an-actormjondalenmonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenapolicemoriyamatsuuramoriyoshiminamiashigaramormonstermoroyamatsuzakis-an-actressmushcdn77-sslingmortgagemoscowithgoogleapiszmoseushimogosenmosjoenmoskenesorreisahayakawakamiichikawamisatottoris-an-anarchistjordalshalsenmossortlandmosviknx-serversusakiyosupabaseminemotegit-reposoruminanomoviemovimientokyotangotembaixadattowebhareidsbergmozilla-iotrentinosuedtirolmtranbytomaridagawalmartrentinsud-tirolmuikaminokawanishiaizubangemukoelnmunakatanemuosattemupkomatsushimassa-carrara-massacarraramassabuzzmurmanskomforbar2murotorcraftranakatombetsumy-gatewaymusashinodesakegawamuseumincomcastoripressorfoldmusicapetownnews-stagingmutsuzawamy-vigormy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoundcastorjdevcloudfunctionsokndalmydattolocalcertificationmyddnsgeekgalaxymydissentrentinsudtirolmydobissmarterthanyoumydrobofageometre-experts-comptablesowamydspectruminisitemyeffectrentinsued-tirolmyfastly-edgekey-stagingmyfirewalledreplittlestargardmyforuminterecifedextraspace-to-rentalstomakomaibaramyfritzmyftpaccesspeedpartnermyhome-servermyjinomykolaivencloud66mymailermymediapchoseikarugalsacemyokohamamatsudamypeplatformsharis-an-artistockholmestrandmypetsphinxn--41amyphotoshibajddarvodkafjordvaporcloudmypictureshinomypsxn--42c2d9amysecuritycamerakermyshopblockspjelkavikommunalforbundmyshopifymyspreadshopselectrentinsuedtirolmytabitordermythic-beastspydebergmytis-a-anarchistg-buildermytuleap-partnersquaresindevicenzamyvnchoshichikashukudoyamakeuppermywirecipescaracallypoivronpokerpokrovskommunepolkowicepoltavalle-aostavernpomorzeszowithyoutuberspacekitagawaponpesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-bykleclerchitachinakagawaltervistaipeigersundynamic-dnsarlpordenonepornporsangerporsangugeporsgrunnanpoznanpraxihuanprdprgmrprimetelprincipeprivatelinkomonowruzhgorodeoprivatizehealthinsuranceprofesionalprogressivegasrlpromonza-e-della-brianzaptokuyamatsushigepropertysnesrvarggatrevisogneprotectionprotonetroandindependent-inquest-a-la-masionprudentialpruszkowiwatsukiyonotaireserve-onlineprvcyonabarumbriaprzeworskogpunyufuelpupulawypussycatanzarowixsitepvhachirogatakahatakaishimojis-a-geekautokeinotteroypvtrogstadpwchowderpzqhadanorthwesternmutualqldqotoyohashimotoshimaqponiatowadaqslgbtroitskomorotsukagawaqualifioapplatter-applatterplcube-serverquangngais-certifiedugit-pagespeedmobilizeroticaltanissettailscaleforcequangninhthuanquangtritonoshonais-foundationquickconnectromsakuragawaquicksytestreamlitapplumbingouvaresearchitectesrhtrentoyonakagyokutoyakomakizunokunimimatakasugais-an-engineeringquipelementstrippertuscanytushungrytuvalle-daostamayukis-into-animeiwamizawatuxfamilytuyenquangbinhthuantwmailvestnesuzukis-gonevestre-slidreggio-calabriavestre-totennishiawakuravestvagoyvevelstadvibo-valentiaavibovalentiavideovinhphuchromedicinagatorogerssarufutsunomiyawakasaikaitakokonoevinnicarbonia-iglesias-carboniaiglesiascarboniavinnytsiavipsinaapplurinacionalvirginanmokurennebuvirtual-userveexchangevirtualservervirtualuserveftpodhalevisakurais-into-carsnoasakuholeckodairaviterboliviajessheimmobilienvivianvivoryvixn--45br5cylvlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavmintsorocabalashovhachiojiyahikobierzycevologdanskoninjambylvolvolkswagencyouvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiechungnamdalseidfjordynathomebuiltwithdarkhangelskypecorittogojomeetoystre-slidrettozawawmemergencyahabackdropalermochizukikirarahkkeravjuwmflabsvalbardunloppadualstackomvuxn--3hcrj9chonanbuskerudynamisches-dnsarpsborgripeeweeklylotterywoodsidellogliastradingworse-thanhphohochiminhadselbuyshouseshirakolobrzegersundongthapmircloudletshiranukamishihorowowloclawekonskowolawawpdevcloudwpenginepoweredwphostedmailwpmucdnipropetrovskygearappodlasiellaknoluoktagajobojis-an-entertainerwpmudevcdnaccessojamparaglidingwritesthisblogoipodzonewroclawmcloudwsseoullensvanguardianwtcp4wtfastlylbanzaicloudappspotagereporthruherecreationinomiyakonojorpelandigickarasjohkameyamatotakadawuozuerichardlillywzmiuwajimaxn--4it797konsulatrobeepsondriobranconagareyamaizuruhrxn--4pvxs4allxn--54b7fta0ccistrondheimpertrixcdn77-secureadymadealstahaugesunderxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49citadelhichisochimkentozsdell-ogliastraderxn--5rtq34kontuminamiuonumatsunoxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264citicarrdrobakamaiorigin-stagingmxn--12co0c3b4evalleaostaobaomoriguchiharaffleentrycloudflare-ipfstcgroupaaskimitsubatamibulsan-suedtirolkuszczytnoopscbgrimstadrrxn--80aaa0cvacationsvchoyodobashichinohealth-carereforminamidaitomanaustdalxn--80adxhksveioxn--80ao21axn--80aqecdr1axn--80asehdbarclaycards3-us-west-1xn--80aswgxn--80aukraanghkeliwebpaaskoyabeagleboardxn--8dbq2axn--8ltr62konyvelohmusashimurayamassivegridxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisencowayxn--90a3academiamicable-modemoneyxn--90aeroportsinfolionetworkangerxn--90aishobaraxn--90amckinseyxn--90azhytomyrxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byanagawaxn--asky-iraxn--aurskog-hland-jnbarclays3-us-west-2xn--avery-yuasakurastoragexn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsvelvikongsbergxn--bck1b9a5dre4civilaviationfabricafederation-webredirectmediatechnologyeongbukashiwazakiyosembokutamamuraxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyanaizuxn--bjddar-ptarumizusawaxn--blt-elabcienciamallamaceiobbcn-north-1xn--bmlo-graingerxn--bod-2natalxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagesquare7xn--brum-voagatrustkanazawaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbarefootballooningjovikarasjoketokashikiyokawaraxn--c1avgxn--c2br7gxn--c3s14misakis-a-therapistoiaxn--cck2b3baremetalombardyn-vpndns3-website-ap-northeast-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-into-cartoonsokamitsuexn--ciqpnxn--clchc0ea0b2g2a9gcdxn--czr694bargainstantcloudfrontdoorestauranthuathienhuebinordre-landiherokuapparochernigovernmentjeldsundiscordsays3-website-ap-southeast-1xn--czrs0trvaroyxn--czru2dxn--czrw28barrel-of-knowledgeapplinziitatebayashijonawatebizenakanojoetsumomodellinglassnillfjordiscordsezgoraxn--d1acj3barrell-of-knowledgecomputermezproxyzgorzeleccoffeedbackanagawarmiastalowa-wolayangroupars3-website-ap-southeast-2xn--d1alfaststacksevenassigdalxn--d1atrysiljanxn--d5qv7z876clanbibaiduckdnsaseboknowsitallxn--davvenjrga-y4axn--djrs72d6uyxn--djty4koobindalxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmail-boxaxn--eckvdtc9dxn--efvn9svn-repostuff-4-salexn--efvy88haebaruericssongdalenviknaklodzkochikushinonsenasakuchinotsuchiurakawaxn--ehqz56nxn--elqq16hagakhanhhoabinhduongxn--eveni-0qa01gaxn--f6qx53axn--fct429kooris-a-nascarfanxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbcleverappsassarinuyamashinazawaxn--fiq64barsycenterprisecloudcontrolappgafanquangnamasteigenoamishirasatochigifts3-website-eu-west-1xn--fiqs8swidnicaravanylvenetogakushimotoganexn--fiqz9swidnikitagatakkomaganexn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbsswiebodzindependent-commissionxn--forlcesena-c8axn--fpcrj9c3dxn--frde-granexn--frna-woaxn--frya-hraxn--fzc2c9e2clickrisinglesjaguarxn--fzys8d69uvgmailxn--g2xx48clinicasacampinagrandebungotakadaemongolianishitosashimizunaminamiawajikintuitoyotsukaidownloadrudtvsaogoncapooguyxn--gckr3f0fastvps-serveronakanotoddenxn--gecrj9cliniquedaklakasamatsudoesntexisteingeekasserversicherungroks-theatrentin-sud-tirolxn--ggaviika-8ya47hagebostadxn--gildeskl-g0axn--givuotna-8yandexcloudxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-into-gamessinamsosnowieconomiasadojin-dslattuminamitanexn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45brj9churcharterxn--gnstigliefern-wobihirosakikamijimayfirstorfjordxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3eveneswinoujsciencexn--h2brj9c8clothingdustdatadetectrani-andria-barletta-trani-andriaxn--h3cuzk1dienbienxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinehimejiiyamanouchikujoinvilleirvikarasuyamashikemrevistathellequipmentjmaxxxjavald-aostatics3-website-sa-east-1xn--hebda8basicserversejny-2xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-k3swisstufftoread-booksnestudioxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyaotsusonoxn--io0a7is-leetrentinoaltoadigexn--j1adpohlxn--j1aefauskedsmokorsetagayaseralingenovaraxn--j1ael8basilicataniaxn--j1amhaibarakisosakitahatakamatsukawaxn--j6w193gxn--jlq480n2rgxn--jlster-byasakaiminatoyookananiimiharuxn--jrpeland-54axn--jvr189misasaguris-an-accountantsmolaquilaocais-a-linux-useranishiaritabashikaoizumizakitashiobaraxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45q11circlerkstagentsasayamaxn--koluokta-7ya57haiduongxn--kprw13dxn--kpry57dxn--kput3is-lostre-toteneis-a-llamarumorimachidaxn--krager-gyasugitlabbvieeexn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfastly-terrariuminamiiseharaxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasuokanmakiwakuratexn--kvnangen-k0axn--l-1fairwindsynology-diskstationxn--l1accentureklamborghinikkofuefukihabororosynology-dsuzakadnsaliastudynaliastrynxn--laheadju-7yatominamibosoftwarendalenugxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52basketballfinanzjaworznoticeableksvikaratsuginamikatagamilanotogawaxn--lesund-huaxn--lgbbat1ad8jejuxn--lgrd-poacctulaspeziaxn--lhppi-xqaxn--linds-pramericanexpresservegame-serverxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacn-northwest-1xn--lten-granvindafjordxn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddesxn--mgb9awbfbsbxn--1qqw23axn--mgba3a3ejtunesuzukamogawaxn--mgba3a4f16axn--mgba3a4fra1-deloittexn--mgba7c0bbn0axn--mgbaakc7dvfsxn--mgbaam7a8haiphongonnakatsugawaxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscountry-snowplowiczeladzlgleezeu-2xn--mgbai9azgqp6jelasticbeanstalkharkovalleeaostexn--mgbayh7gparasitexn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskopervikhmelnytskyivalleedaostexn--mgbqly7c0a67fbcngroks-thisayamanobeatsaudaxn--mgbqly7cvafricargoboavistanbulsan-sudtirolxn--mgbt3dhdxn--mgbtf8flatangerxn--mgbtx2bauhauspostman-echofunatoriginstances3-website-us-east-1xn--mgbx4cd0abkhaziaxn--mix082fbx-osewienxn--mix891fbxosexyxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44cnpyatigorskjervoyagexn--mkru45is-not-certifiedxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakuratanxn--mosjen-eyatsukannamihokksundxn--mot-tlavangenxn--mre-og-romsdal-qqbuservecounterstrikexn--msy-ula0hair-surveillancexn--mtta-vrjjat-k7aflakstadaokayamazonaws-cloud9guacuiababybluebiteckidsmynasushiobaracingrok-freeddnsfreebox-osascoli-picenogatabuseating-organicbcgjerdrumcprequalifymelbourneasypanelblagrarq-authgear-stagingjerstadeltaishinomakilovecollegefantasyleaguenoharauthgearappspacehosted-by-previderehabmereitattoolforgerockyombolzano-altoadigeorgeorgiauthordalandroideporteatonamidorivnebetsukubankanumazuryomitanocparmautocodebergamoarekembuchikumagayagawafflecelloisirs3-external-180reggioemiliaromagnarusawaustrheimbalsan-sudtirolivingitpagexlivornobserveregruhostingivestbyglandroverhalladeskjakamaiedge-stagingivingjemnes3-eu-west-2038xn--muost-0qaxn--mxtq1misawaxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4dbgdty6ciscofreakamaihd-stagingriwataraindroppdalxn--nit225koryokamikawanehonbetsuwanouchikuhokuryugasakis-a-nursellsyourhomeftpiwatexn--nmesjevuemie-tcbalatinord-frontierxn--nnx388axn--nodessakurawebsozais-savedxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsicilynxn--4dbrk0cexn--o3cw4hakatanortonkotsunndalxn--o3cyx2axn--od0algardxn--od0aq3beneventodayusuharaxn--ogbpf8fldrvelvetromsohuissier-justicexn--oppegrd-ixaxn--ostery-fyatsushiroxn--osyro-wuaxn--otu796dxn--p1acfedjeezxn--p1ais-slickharkivallee-d-aostexn--pgbs0dhlx3xn--porsgu-sta26fedorainfraclouderaxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cnsauheradyndns-at-homedepotenzamamicrosoftbankasukabedzin-brbalsfjordietgoryoshiokanravocats3-fips-us-gov-west-1xn--qcka1pmcpenzapposxn--qqqt11misconfusedxn--qxa6axn--qxamunexus-3xn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-uberleetrentinos-tirolxn--rennesy-v1axn--rhkkervju-01afedorapeoplefrakkestadyndns-webhostingujogaszxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturalxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byawaraxn--rny31hakodatexn--rovu88bentleyusuitatamotorsitestinglitchernihivgubs3-website-us-west-1xn--rros-graphicsxn--rskog-uuaxn--rst-0naturbruksgymnxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawatahamaxn--s-1faitheshopwarezzoxn--s9brj9cntraniandriabarlettatraniandriaxn--sandnessjen-ogbentrendhostingliwiceu-3xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4gbriminiserverxn--skierv-utazurestaticappspaceusercontentunkongsvingerxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navigationxn--slt-elabogadobeaemcloud-fr1xn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbeppublishproxyuufcfanirasakindependent-panelomonza-brianzaporizhzhedmarkarelianceu-4xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbeskidyn-ip24xn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bloggerxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbestbuyshoparenagasakikuchikuseihicampinashikiminohostfoldnavyuzawaxn--stre-toten-zcbetainaboxfuselfipartindependent-reviewegroweibolognagasukeu-north-1xn--t60b56axn--tckweddingxn--tiq49xqyjelenia-goraxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbhzc66xn--trentin-sdtirol-7vbialystokkeymachineu-south-1xn--trentino-sd-tirol-c3bielawakuyachimataharanzanishiazaindielddanuorrindigenamerikawauevje-og-hornnes3-website-us-west-2xn--trentino-sdtirol-szbiella-speziaxn--trentinosd-tirol-rzbieszczadygeyachiyodaeguamfamscompute-1xn--trentinosdtirol-7vbievat-band-campaignieznoorstaplesakyotanabellunordeste-idclkarlsoyxn--trentinsd-tirol-6vbifukagawalbrzycharitydalomzaporizhzhiaxn--trentinsdtirol-nsbigv-infolkebiblegnicalvinklein-butterhcloudiscoursesalangenishigotpantheonsitexn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atventuresinstagingxn--uc0ay4axn--uist22hakonexn--uisz3gxn--unjrga-rtashkenturindalxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbturystykaneyamazoexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccertmgreaterxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbiharstadotsubetsugarulezajskiervaksdalondonetskarmoyxn--vestvgy-ixa6oxn--vg-yiabruzzombieidskogasawarackmazerbaijan-mayenbaidarmeniaxn--vgan-qoaxn--vgsy-qoa0jellybeanxn--vgu402coguchikuzenishiwakinvestmentsaveincloudyndns-at-workisboringsakershusrcfdyndns-blogsitexn--vhquvestfoldxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bihoronobeokagakikugawalesundiscoverdalondrinaplesknsalon-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1communexn--wgbl6axn--xhq521bikedaejeonbuk0xn--xkc2al3hye2axn--xkc2dl3a5ee0hakubackyardshiraois-a-greenxn--y9a3aquarelleasingxn--yer-znavois-very-badxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4it168dxn--ystre-slidre-ujbiofficialorenskoglobodoes-itcouldbeworldishangrilamdongnairkitapps-audibleasecuritytacticsxn--0trq7p7nnishiharaxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bipartsaloonishiizunazukindustriaxnbayernxz
\ No newline at end of file
+bolzano-altoadigevje-og-hornnes3-website-us-west-2bomlocustomer-ocienciabonavstackarasjoketokuyamashikokuchuobondigitaloceanspacesakurastoragextraspace-to-rentalstomakomaibarabonesakuratanishikatakazakindustriesteinkjerepbodynaliasnesoddeno-staginglobodoes-itcouldbeworfarsundiskussionsbereichateblobanazawarszawashtenawsapprunnerdpoliticaarparliamenthickarasuyamasoybookonlineboomladeskierniewiceboschristmasakilovecollegefantasyleaguedagestangebostik-serveronagasukeyword-oncillahppictetcieszynishikatsuragit-repostre-totendofinternet-dnsakurawebredirectmeiwamizawabostonakijinsekikogentlentapisa-geekaratsuginamikatagamimozaporizhzhegurinfinitigooglecode-builder-stg-buildereporthruhereclaimsakyotanabellunord-odalvdalcest-le-patron-k3salangenishikawazukamishihorobotdashgabadaddjabbotthuathienhuebouncemerckmsdscloudisrechtrafficplexus-4boutiquebecologialaichaugianglogowegroweibolognagasakikugawaltervistaikillondonetskarelianceboutireserve-onlineboyfriendoftheinternetflixn--11b4c3ditchyouriparmabozen-sudtirolondrinaplesknsalatrobeneventoeidsvollorenskogloomy-gatewaybozen-suedtirolovableprojectjeldsundivtasvuodnakamai-stagingloppennebplaceditorxn--12c1fe0bradescotaruinternationalovepoparochernihivgubamblebtimnetzjaworznotebook-fips3-fips-us-gov-east-1brandivttasvuotnakamuratajirintlon-2brasiliadboxoslodingenishimerabravendbarcelonagawakuyabukikiraragusabaerobatickets3-fips-us-gov-west-1bresciaogashimadachicappabianiceobridgestonebrindisiciliabroadwaybroke-itvedestrandixn--12cfi8ixb8lovesickarlsoybrokerevistathellebrothermesserlidlplfinancialpusercontentjmaxxxn--12co0c3b4evalleaostargets-itjomeldalucaniabrumunddaluccampobassociatesalon-1brusselsaloonishinomiyashironobryanskiervadsoccerhcloudyclusterbrynebweirbzhitomirumaintenanceclothingdustdatadetectoyouracngovtoystre-slidrettozawacnpyatigorskjakamaiedge-stagingreatercnsapporocntozsdeliverycodebergrayjayleaguesardegnarutoshimatta-varjjatranatalcodespotenzakopanecoffeedbackanagawatsonrendercommunity-prochowicecomockashiharacompanyantaishinomakimobetsulifestylefrakkestadurumisakindlegnicahcesuolohmusashimurayamaizuruhr-uni-bochuminamiechizenisshingucciminamifuranocomparemarkerryhotelsardiniacomputercomsecretrosnubarclays3-me-south-1condoshiibabymilk3conferenceconstructioniyodogawaconsuladobeio-static-accesscamdvrcampaniaconsultantranbyconsultingretakamoriokakudamatsuecontactivetrail-central-1contagematsubaracontractorstabacgiangiangryconvexecute-apictureshinordkappaviacookingrimstadynathomebuiltwithdarklangevagrarchitectestingripeeweeklylotterycooperativano-frankivskjervoyagecoprofesionalchikugodaddyn-o-saureadymadethis-a-anarchistjordalshalsenl-ams-1corsicafederationfabricable-modemoneycosenzamamidorivnecosidnsdojoburgriwataraindroppdalcouchpotatofriesarlcouncilcouponstackitagawacozoracpservernamegataitogodoesntexisteingeekashiwaracqcxn--1lqs71dyndns-at-homedepotrani-andria-barletta-trani-andriacrankyotobetsulubin-dsldyndns-at-workisboringsakershusrcfdyndns-blogsiteleaf-south-1crdyndns-freeboxosarpsborgroks-theatrentin-sud-tirolcreditcardyndns-homednsarufutsunomiyawakasaikaitakokonoecreditunioncremonasharis-a-bulls-fancrewp2cricketnedalcrimeast-kazakhstanangercrispawnextdirectraniandriabarlettatraniandriacrminamiiseharacrotonecrownipfizercrsasayamacruisesaseboknowsitallcryptonomichiharacuisinellamdongnairflowersassaris-a-candidatecuneocuritibackdropalermobarag-cloud-charitydalp1cutegirlfriendyndns-ipgwangjulvikashiwazakizunokuniminamiashigarafedoraprojectransiphdfcbankasserverrankoshigayakagefeirafembetsukubankasukabeautypedreamhosterscrapper-sitefermodalenferraraferraris-a-celticsfanferreroticallynxn--2scrj9cargoboavistanbulsan-sudtiroluhanskarmoyfetsundyndns-remotewdhlx3fgroundhandlingroznyfhvalerfilegear-sg-1filminamiminowafinalfinancefinnoyfirebaseapphilipscrappingrphonefosscryptedyndns-serverdalfirenetgamerscrysecuritytacticscwestus2firenzeaburfirestonefirmdaleilaocairportranslatedyndns-webhareidsbergroks-thisayamanobearalvahkikonaikawachinaganoharamcoachampionshiphoplixn--1qqw23afishingokasellfyresdalfitjarfitnessettsurugashimamurogawafjalerfkasumigaurayasudaflesbergrueflickragerotikagoshimandalflierneflirflogintohmangoldpoint2thisamitsukefloppymntransportefloraclegovcloudappservehttpbincheonflorencefloripadualstackasuyakumoduminamioguni5floristanohatakaharunservehumourfloromskoguidefinimalopolskanittedalfltransurlflutterflowhitesnowflakeflyfncarrdiyfndyndns-wikinkobayashimofusadojin-the-bandairlinemurorangecloudplatformshakotanpachihayaakasakawaharacingrondarfoolfor-ourfor-somedusajserveircasacampinagrandebulsan-suedtirolukowesleyfor-theaterfordebianforexrotheworkpccwhminamisanrikubetsupersaleksvikaszubytemarketingvollforgotdnserveminecraftrapanikkoelnforli-cesena-forlicesenaforlikescandypopensocialforsalesforceforsandasuoloisirservemp3fortalfosneservep2photographysiofotravelersinsurancefoxn--30rr7yfozfr-1fr-par-1fr-par-2franalytics-gatewayfredrikstadyndns-worksauheradyndns-mailfreedesktopazimuthaibinhphuocprapidyndns1freemyiphostyhostinguitarservepicservequakefreesitefreetlservesarcasmilefreightravinhlonganfrenchkisshikirovogradoyfreseniuservicebuskerudynnsaveincloudyndns-office-on-the-webflowtest-iservebloginlinefriuli-v-giuliarafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfrogansevastopolitiendafrognfrolandynservebbsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriafrom-akamaiorigin-stagingujaratmetacentruminamitanefrom-alfrom-arfrom-azureedgecompute-1from-caltanissettainaircraftraeumtgeradealstahaugesunderfrom-cockpitrdynuniversitysvardofrom-ctrentin-sudtirolfrom-dcasertaipeigersundnparsaltdaluroyfrom-decafjsevenassieradzfrom-flatangerfrom-gap-southeast-3from-higashiagatsumagoianiafrom-iafrom-idynv6from-ilfrom-in-vpncashorokanaiefrom-ksewhoswholidayfrom-kyfrom-langsonyatomigrationfrom-mangyshlakamaized-stagingujohanamakinoharafrom-mdynvpnplusavonarviikamisatokonamerikawauefrom-meetrentin-sued-tirolfrom-mihamadanangoguchilloutsystemscloudscalebookinghosteurodirfrom-mnfrom-modellingulenfrom-msexyfrom-mtnfrom-ncasinordeste-idclkarpaczest-a-la-maisondre-landray-dnsaludrayddns-ipartintuitjxn--1ck2e1barclaycards3-globalatinabelementorayomitanobservableusercontentateyamauth-fipstmninomiyakonojosoyrovnoticeableitungsenirasakibxos3-ca-central-180reggio-emilia-romagnaroyolasitebinordlandeus-canvasitebizenakanojogaszkolamericanfamilyds3-ap-south-12hparallelimodxboxeroxjavald-aostaticsxmitakeharaugustow-corp-staticblitzgorzeleccocotteatonamifunebetsuikirkenes3-ap-northeast-2ixn--0trq7p7nninjambylive-oninohekinanporovigonnakasatsunaibigawaukraanghkembuchikumagayagawakkanaibetsubame-central-123websitebuildersvp4from-ndyroyrvikingrongrossetouchijiwadedyn-berlincolnfrom-nefrom-nhlfanfrom-njsheezyfrom-nminamiuonumatsunofrom-nvalled-aostargithubusercontentrentin-suedtirolfrom-nysagamiharafrom-ohdancefrom-okegawafrom-orfrom-palmasfjordenfrom-pratohnoshookuwanakanotoddenfrom-ris-a-chefashionstorebaseljordyndns-picsbssaudafrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--32vp30hachinoheavyfrom-utsiracusagemakerfrom-val-daostavalleyfrom-vtrentino-a-adigefrom-wafrom-wiardwebspaceconfigunmarnardalfrom-wvalledaostarnobrzeguovdageaidnunjargausdalfrom-wyfrosinonefrostalowa-wolawafroyal-commissionfruskydivingushikamifuranorth-kazakhstanfujiiderafujikawaguchikonefujiminokamoenairtelebitbucketrzynh-servebeero-stageiseiroutingthecloudfujinomiyadappnodearthainguyenfujiokazakiryuohkurafujisatoshoeshellfujisawafujishiroishidakabiratoridediboxafujitsuruokakamigaharafujiyoshidatsunanjoetsumidaklakasamatsudogadobeioruntimedicinakaiwanairforcentralus-1fukayabeagleboardfukuchiyamadattorelayfukudomigawafukuis-a-conservativefsnoasakakinokiafukumitsubishigakisarazure-apigeefukuokakegawafukuroishikariwakunigamiharuovatlassian-dev-builderfukusakishiwadattoweberlevagangaviikanonjis-a-cpanelfukuyamagatakahashimamakisofukushimaniwamannordre-landfunabashiriuchinadavvenjargamvikatowicefunagatakahatakaishimokawafunahashikamiamakusatsumasendaisenergyeonggiizefundfunkfeuerfunnelshimonitayanagitapphutholdingsmall-websozais-a-cubicle-slaveroykenfuoiskujukuriyamaoris-a-democratrentino-aadigefuosskodjeezfurubirafurudonordreisa-hockeynutwentertainmentrentino-alto-adigefurukawaiishoppingxn--3bst00minamiyamashirokawanabeepsondriobranconagarahkkeravjunusualpersonfusoctrangyeongnamdinhs-heilbronnoysundfussaikisosakitahatakamatsukawafutabayamaguchinomihachimanagementrentino-altoadigefutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinairtrafficmanagerfuturecmshimonosekikawafuturehosting-clusterfuturemailingzfvghakuis-a-doctoruncontainershimotsukehakusandnessjoenhaldenhalfmoonscaleforcehalsaitamatsukuris-a-financialadvisor-aurdalham-radio-ophuyenhamburghammarfeastasiahamurakamigoris-a-fullstackaufentigerhanamigawahanawahandahandcraftedugit-pages-researchedmarketplacehangglidinghangoutrentino-s-tirolhannannestadhannoshiroomghanoiphxn--3ds443ghanyuzenhappoumuginowaniihamatamakawajimap-southeast-4hasamazoncognitoigawahasaminami-alpshimotsumahashbanghasudahasura-appigboatshinichinanhasvikautokeinotionhatenablogspotrentino-stirolhatenadiaryhatinhachiojiyachiyodazaifudaigojomedio-campidano-mediocampidanomediohatogayachtshinjournalistorfjordhatoyamazakitakatakanezawahatsukaichikawamisatohokkaidontexistmein-iservschulegalleryhattfjelldalhayashimamotobusells-for-lesshinjukuleuvenicehazuminobushibuyahabacninhbinhdinhktrentino-sud-tirolhelpgfoggiahelsinkitakyushunantankazohemneshinkamigotoyokawahemsedalhepforgeblockshinshinotsupplyhetemlbfanheyflowienhigashichichibuzzhigashihiroshimanehigashiizumozakitamihokksundhigashikagawahigashikagurasoedahigashikawakitaaikitamotosumy-routerhigashikurumegurownproviderhigashimatsushimarriottrentino-sudtirolhigashimatsuyamakitaakitadaitomanaustdalhigashimurayamamotorcycleshinshirohigashinarusells-for-uzhhorodhigashinehigashiomitamamurausukitanakagusukumodshintokushimahigashiosakasayamanakakogawahigashishirakawamatakaokalmykiahigashisumiyoshikawaminamiaikitashiobarahigashitsunospamproxyhigashiurawa-mazowszexposeducatorprojectrentino-sued-tirolhigashiyamatokoriyamanashijonawatehigashiyodogawahigashiyoshinogaris-a-geekazunotogawahippythonanywherealminanohiraizumisatokaizukaluganskddiamondshintomikasaharahirakatashinagawahiranais-a-goodyearhirarahiratsukagawahirayahikobeatshinyoshitomiokamisunagawahitachiomiyakehitachiotaketakarazukamaishimodatehitradinghjartdalhjelmelandholyhomegoodshiojirishirifujiedahomeipikehomelinuxn--3e0b707ehomesecuritymacaparecidahomesecuritypcateringebungotakadaptableclerc66116-balsfjordeltaiwanumatajimidsundeportebinatsukigatakahamalvik8s3-ap-northeast-3utilities-12charstadaokagakirunocelotenkawadlugolekadena4ufcfanimsiteasypanelblagrigentobishimafeloansncf-ipfstdlibestadultatarantoyonakagyokutoyonezawapartments3-ap-northeast-123webseiteckidsmynascloudfrontierimo-siemenscaledekaascolipicenoboribetsubsc-paywhirlimitedds3-accesspoint-fips3-ap-east-123miwebaccelastx4432-b-datacenterprisesakihokuizumoarekepnord-aurdalipaynow-dns-dynamic-dnsabruzzombieidskogasawarackmazerbaijan-mayenbaidarmeniajureggio-calabriaknoluoktagajoboji234lima-citychyattorneyagawafflecellclstagehirnayorobninsk123kotisivultrobjectselinogradimo-i-ranamizuhobby-siteaches-yogano-ip-ddnsgeekgalaxyzgierzgorakrehamnfshostrowwlkpnftstorage164-balsan-suedtirolillyokozeastus2000123paginawebadorsiteshikagamiishibechambagricoharugbydgoszczecin-addrammenuorogerscbgdyniaktyubinskaunicommuneustarostwodzislawdev-myqnapcloudflarecn-northwest-123sitewebcamauction-acornikonantotalimanowarudakunexus-2038homesenseeringhomeskleppilottottoris-a-greenhomeunixn--3hcrj9catfoodraydnsalvadorhondahonjyoitakasagonohejis-a-guruzshioyaltakkolobrzegersundongthapmircloudnshome-webservercelliguriahornindalhorsells-itrentino-suedtirolhorteneiheijis-a-hard-workershirahamatonbetsupportrentinoa-adigehospitalhotelwithflightshirakomaganehotmailhoyangerhoylandetakasakitaurahrsnillfjordhungyenhurdalhurumajis-a-hunterhyllestadhyogoris-a-knightpointtokashikitchenhypernodessaitokamachippubetsubetsugaruhyugawarahyundaiwafuneis-uberleetrentinoaltoadigeis-very-badis-very-evillasalleirvikharkovallee-d-aosteis-very-goodis-very-niceis-very-sweetpepperugiais-with-thebandoomdnsiskinkyowariasahikawaisk01isk02jellybeanjenv-arubahcavuotnagahamaroygardenflfanjeonnamsosnowiecaxiaskoyabenoopssejny-1jetztrentinos-tiroljevnakerjewelryjlljls-sto1jls-sto2jls-sto365jmpioneerjnjcloud-ver-jpcatholicurus-3joyentrentinostiroljoyokaichibahccavuotnagaivuotnagaokakyotambabybluebitemasekd1jozis-a-llamashikiwakuratejpmorgangwonjpnjprshoujis-a-musiciankoseis-a-painterhostsolutionshiraokamitsuekosheroykoshimizumakis-a-patsfankoshugheshwiiheyahoooshikamagayaitakashimarshallstatebankhplaystation-cloudsitekosugekotohiradomainsurealtypo3serverkotourakouhokumakogenkounosunnydaykouyamatlabcn-north-1kouzushimatrixn--41akozagawakozakis-a-personaltrainerkozowilliamhillkppspdnsigdalkrasnikahokutokyotangopocznore-og-uvdalkrasnodarkredumbrellapykrelliankristiansandcatsiiitesilklabudhabikinokawabajddarqhachirogatakanabeardubaioiraseekatsushikabedzin-brb-hostingkristiansundkrodsheradkrokstadelvaldaostavangerkropyvnytskyis-a-photographerokuappinkfh-muensterkrymisasaguris-a-playershiftrentinoaadigekumamotoyamatsumaebashimogosenkumanowtvalleedaostekumatorinokumejimatsumotofukekumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-republicanonoichinosekigaharakunitachiaraisaijorpelandkunitomigusukukis-a-rockstarachowicekunneppubtlsimple-urlkuokgroupiwatekurgankurobeebyteappleykurogiminamiawajikis-a-socialistockholmestrandkuroisodegaurakuromatsunais-a-soxfankuronkurotakikawasakis-a-studentalkushirogawakustanais-a-teacherkassyncloudkusupabaseminekutchanelkutnokuzumakis-a-techietis-a-liberalkvafjordkvalsundkvamfamplifyappchizip6kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspectrumisawamjondalenmonza-brianzapposirdalmonza-e-della-brianzaptonsbergmonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenapolicemoriyamatsushigemoriyoshiminamibosoftwarendalenugmormonstermoroyamatsuuramortgagemoscowinbarrel-of-knowledgekey-stagingjerstadigickaracolognemrstudio-prodoyonagoyauthgearapps-1and1moseushimoichikuzenmosjoenmoskenesiskomakis-a-therapistoiamosslupskmpspbaremetalpha-myqnapcloudaccess3-sa-east-1mosviknx-serversicherungmotegirlymoviemovimientoolslzmtrainingmuikamiokameokameyamatotakadamukodairamunakatanemuosattemupixolinodeusercontentrentinosud-tirolmurmanskomatsushimasudamurotorcraftrentinosudtirolmusashinodesakatakayamatsuzakis-an-accountantshiratakahagiangmuseumisconfusedmusicanthoboleslawiecommerce-shopitsitevaksdalmutsuzawamutualmy-vigormy-wanggoupilemyactivedirectorymyaddrangedalmyamazeplaymyasustor-elvdalmycloudnasushiobaramydattolocalcertrentinosued-tirolmydbservermyddnskingmydissentrentinosuedtirolmydnsmolaquilarvikomforbargainstitutemp-dnswatches3-us-east-2mydobissmarterthanyoumydrobofageorgeorgiamydsmushcdn77-securecipescaracalculatorskenmyeffectrentinsud-tirolmyfastly-edgemyfirewalledreplittlestargardmyforumishimatsusakahoginozawaonsennanmokurennebuyshousesimplesitemyfritzmyftpaccessojampanasonichernovtsydneymyhome-servermyjinomykolaivencloud66mymailermymediapchiryukyuragifuchungbukharanzanishinoomotegoismailillehammerfeste-ipartsamegawamynetnamegawamyokohamamatsudamypepizzamypetsokananiimilanoticiassurfastly-terrariuminamiizukaminoyamaxunison-servicesaxomyphotoshibalena-devicesokndalmypiemontemypsxn--42c2d9amyrdbxn--45br5cylmysecuritycamerakermyshopblocksolardalmyshopifymyspreadshopselectrentinsudtirolmytabitordermythic-beastsolundbeckommunalforbundmytis-a-bloggermytuleap-partnersomamyvnchitachinakagawassamukawatarittogitsuldalutskartuzymywirebungoonoplurinacionalpmnpodhalepodlasiellakdnepropetrovskanlandpodzonepohlpoivronpokerpokrovskomonotteroypolkowicepoltavalle-aostavernpolyspacepomorzeszowindowsserveftplatter-appkommuneponpesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-an-actresshishikuis-a-libertarianpordenonepornporsangerporsangugeporsgrunnanpoznanpraxihuanprdprereleaseoullensakerprgmrprimetelprincipenzaprivatelinkyard-cloudletsomnarvikomorotsukaminokawanishiaizubangeprivatizehealthinsuranceprogressivegarsheiyufueliv-dnsoowinepromoliserniapropertysnesopotrentinsued-tirolprotectionprotonetrentinsuedtirolprudentialpruszkowinnersor-odalprvcyprzeworskogpunyukis-an-anarchistoloseyouripinokofuefukihabororoshisogndalpupulawypussycatanzarowiosor-varangerpvhackerpvtrentoyosatoyookaneyamazoepwchitosetogliattipsamnangerpzqotoyohashimotoyakokamimineqponiatowadaqslgbtrevisognequalifioapplatterpl-wawsappspacehostedpicardquangngais-an-artistordalquangninhthuanquangtritonoshonais-an-engineeringquickconnectroandindependent-inquest-a-la-masionquicksytesorfoldquipelementsorocabalestrandabergamochizukijobservablehqldquizzesorreisahayakawakamiichinomiyagithubpreviewskrakowitdkontoguraswinoujscienceswissphinxn--45brj9chonanbunkyonanaoshimaringatlanbibaiduckdnsamparachutinglugsjcbnpparibashkiriasyno-dspjelkavikongsbergsynology-diskstationsynology-dspockongsvingertushungrytuvalle-daostaobaolbia-tempio-olbiatempioolbialowiezaganquangnamasteigenoamishirasatochigiftsrhtrogstadtuxfamilytuyenquangbinhthuantwmailvegasrlvelvetromsohuissier-justiceventurestaurantrustkanieruchomoscientistoripresspydebergvestfoldvestnesrvaomoriguchiharaffleentrycloudflare-ipfsortlandvestre-slidrecreationvestre-totennishiawakuravestvagoyvevelstadvfstreakusercontentroitskoninfernovecorealtorvibo-valentiavibovalentiavideovinhphuchoshichikashukudoyamakeupartysfjordrivelandrobakamaihd-stagingmbhartinnishinoshimattelemarkhangelskaruizawavinnicapitalonevinnytsiavipsinaapplockervirginankokubunjis-byklecznagatorokunohealth-carereformincommbankhakassiavirtual-uservecounterstrikevirtualservervirtualuserveexchangevisakuholeckobierzyceviterboliviajessheimperiavivianvivoryvixn--45q11chowdervlaanderennesoyvladikavkazimierz-dolnyvladimirvlogisticstreamlitapplcube-serversusakis-an-actorvmitourismartlabelingvolvologdanskontumintshowavolyngdalvoorlopervossevangenvotevotingvotoyotap-southeast-5vps-hostreaklinkstrippervusercontentrvaporcloudwiwatsukiyonotairesindevicenzaokinawashirosatochiokinoshimagazinewixsitewixstudio-fipstrynwjgorawkzwloclawekonyvelolipopmcdirwmcloudwmelhustudynamisches-dnsorumisugitomobegetmyipifony-2wmflabstuff-4-salewoodsidell-ogliastrapiapplinzis-certifiedworldworse-thanhphohochiminhadanorthflankatsuyamassa-carrara-massacarraramassabunzenwowithgoogleapiszwpdevcloudwpenginepoweredwphostedmailwpmucdn77-sslingwpmudevelopmentrysiljanewaywpsquaredwritesthisblogoiplumbingotpantheonsitewroclawsglobalacceleratorahimeshimakanegasakievennodebalancernwtcp4wtfastlylbarefootballooningjerdrumemergencyonabarumemorialivornobservereitatsunofficialolitapunkapsienamsskoganeindependent-panelombardiademfakefurniturealestatefarmerseinemrnotebooks-prodeomniwebthings3-object-lambdauthgear-stagingivestbyglandroverhallair-traffic-controllagdenesnaaseinet-freaks3-deprecatedgcagliarissadistgstagempresashibetsukuiitatebayashikaoirmembers3-eu-central-1kapp-ionosegawafaicloudineat-urlive-websitehimejibmdevinapps3-ap-southeast-1337wuozuerichardlillesandefjordwwwithyoutuberspacewzmiuwajimaxn--4it797koobindalxn--4pvxs4allxn--54b7fta0cchromediatechnologyeongbukarumaifmemsetkmaxxn--1ctwolominamatarpitksatmalluxenishiokoppegardrrxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49chungnamdalseidfjordtvsangotsukitahiroshimarcherkasykkylvenneslaskerrypropertiesanjotelulublindesnesannanishitosashimizunaminamidaitolgaularavellinodeobjectsannoheliohostrodawaraxn--5rtq34kooris-a-nascarfanxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264churchaselfipirangallupsunappgafanishiwakinuyamashinazawaxn--80aaa0cvacationstufftoread-booksnesoundcastreak-linkomvuxn--3pxu8khmelnitskiyamassivegridxn--80adxhksurnadalxn--80ao21axn--80aqecdr1axn--80asehdbarrell-of-knowledgesuite-stagingjesdalombardyn-vpndns3-us-gov-east-1xn--80aswgxn--80audnedalnxn--8dbq2axn--8ltr62kopervikhmelnytskyivalleeaostexn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisencoreapiacenzachpomorskiengiangxn--90a3academiamibubbleappspotagerxn--90aeroportsinfolkebibleasingrok-freeddnsfreebox-osascoli-picenogatachikawakayamadridvagsoyerxn--90aishobaraoxn--90amckinseyxn--90azhytomyradweblikes-piedmontuckerxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byameloyxn--asky-iraxn--aurskog-hland-jnbarsycenterprisecloudbeesusercontentattoolforgerockyonagunicloudiscordsays3-us-gov-west-1xn--avery-yuasakuragawaxn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbarsyonlinequipmentaveusercontentawktoyonomurauthordalandroidienbienishiazaiiyamanouchikujolsterehabmereisenishigotembaixadavvesiidaknongivingjemnes3-eu-north-1xn--bck1b9a5dre4ciprianiigatairaumalatvuopmicrosoftbankasaokamikoaniikappudopaaskvollocaltonetlifyinvestmentsanokashibatakatsukiyosembokutamakiyosunndaluxuryxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2hosted-by-previderxn--bjarky-fyanagawaxn--bjddar-ptarumizusawaxn--blt-elabkhaziamallamaceiobbcircleaningmodelscapetownnews-stagingmxn--1lqs03nissandoyxn--bmlo-grafana-developerauniterois-coolblogdnshisuifuettertdasnetzxn--bod-2naturalxn--bozen-sdtirol-2obihirosakikamijimayfirstorjdevcloudjiffyxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagespeedmobilizeropslattumbriaxn--brum-voagatulaspeziaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbasicserver-on-webpaaskimitsubatamicrolightingjovikaragandautoscanaryggeemrappui-productions3-eu-west-1xn--c1avgxn--c2br7gxn--c3s14mitoyoakexn--cck2b3basilicataniavocats3-eu-west-2xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-foundationxn--ciqpnxn--clchc0ea0b2g2a9gcdn77-storagencymrulezajskiptveterinaireadthedocs-hostedogawarabikomaezakishimabarakawagoexn--czr694basketballfinanzlgkpmglassessments3-us-west-1xn--czrs0t0xn--czru2dxn--d1acj3batsfjordiscordsezpisdnipropetrovskygearapparasiteu-2xn--d1alfastvps-serverisignxn--d1atunesquaresinstagingxn--d5qv7z876ciscofreakadns-cloudflareglobalashovhachijoinvilleirfjorduponthewifidelitypeformesswithdnsantamariakexn--davvenjrga-y4axn--djrs72d6uyxn--djty4koryokamikawanehonbetsuwanouchikuhokuryugasakis-a-nursellsyourhomeftpinbrowsersafetymarketshiraois-a-landscaperspectakasugais-a-lawyerxn--dnna-graingerxn--drbak-wuaxn--dyry-iraxn--e1a4cistrondheimeteorappassenger-associationissayokoshibahikariyalibabacloudcsantoandrecifedexperts-comptablesanukinzais-a-bruinsfanissedalvivanovoldaxn--eckvdtc9dxn--efvn9surveysowaxn--efvy88hadselbuzentsujiiexn--ehqz56nxn--elqq16haebaruericssongdalenviknakatombetsumitakagildeskaliszxn--eveni-0qa01gaxn--f6qx53axn--fct429kosaigawaxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbcitadelhichisochimkentmpatriaxn--fiq64bauhauspostman-echofunatoriginstances3-us-west-2xn--fiqs8susonoxn--fiqz9suzakarpattiaaxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbentleyoriikarasjohkamikitayamatsurindependent-review-credentialless-staticblitzw-staticblitzxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grajewolterskluwerxn--frna-woaxn--frya-hraxn--fzc2c9e2citicaravanylvenetogakushimotoganexn--fzys8d69uvgmailxn--g2xx48civilaviationionjukujitawaravennaharimalborkdalxn--gckr3f0fauskedsmokorsetagayaseralingenovaraxn--gecrj9clancasterxn--ggaviika-8ya47hagakhanhhoabinhduongxn--gildeskl-g0axn--givuotna-8yanaizuxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-gonexn--gmqw5axn--gnstigbestellen-zvbentrendhostingleezeu-3xn--gnstigliefern-wobiraxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3evenesuzukanazawaxn--h2brj9c8cldmail-boxfuseljeducationporterxn--h3cuzk1dielddanuorris-into-animein-vigorlicexn--hbmer-xqaxn--hcesuolo-7ya35beppublic-inquiryoshiokanumazuryurihonjouwwebhoptokigawavoues3-eu-west-3xn--hebda8beskidyn-ip24xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-fleeklogesquare7xn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyandexcloudxn--io0a7is-into-carshitaramaxn--j1adpdnsupdaterxn--j1aefbsbxn--2m4a15exn--j1ael8bestbuyshoparenagareyamagentositenrikuzentakataharaholtalengerdalwaysdatabaseballangenkainanaejrietiengiangheannakadomarineen-rootaribeiraogakicks-assnasaarlandiscountry-snowplowiczeladzxn--j1amhagebostadxn--j6w193gxn--jlq480n2rgxn--jlster-byaotsurgeryxn--jrpeland-54axn--jvr189mittwaldserverxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--4dbgdty6choyodobashichinohealthcareersamsclubartowest1-usamsungminakamichikaiseiyoichipsandvikcoromantovalle-d-aostakinouexn--koluokta-7ya57haibarakitakamiizumisanofidonnakaniikawatanaguraxn--kprw13dxn--kpry57dxn--kput3is-into-cartoonshizukuishimojis-a-linux-useranishiaritabashikshacknetlibp2pimientaketomisatourshiranukamitondabayashiogamagoriziaxn--krager-gyasakaiminatoyotomiyazakis-into-gamessinaklodzkochikushinonsenasakuchinotsuchiurakawaxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfirmalselveruminisitexn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasugitlabbvieeexn--kvnangen-k0axn--l-1fairwindsuzukis-an-entertainerxn--l1accentureklamborghinikolaeventsvalbardunloppadoval-d-aosta-valleyxn--laheadju-7yasuokannamimatakatoris-leetrentinoalto-adigexn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52bhzc01xn--lesund-huaxn--lgbbat1ad8jejuxn--lgrd-poacctfcloudflareanycastcgroupowiat-band-campaignoredstonedre-eikerxn--lhppi-xqaxn--linds-pramericanexpresservegame-serverxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liaclerkstagentsaobernardovre-eikerxn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddesvchoseikarugalsacexn--mgb9awbfbx-oschokokekscholarshipschoolbusinessebytomaridagawarmiastapleschoolsztynsetranoyxn--mgba3a3ejtunkonsulatinowruzhgorodxn--mgba3a4f16axn--mgba3a4fra1-dellogliastraderxn--mgba7c0bbn0axn--mgbaam7a8haiduongxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00bialystokkeymachineu-4xn--mgbai9azgqp6jelasticbeanstalkhersonlanxesshizuokamogawaxn--mgbayh7gparaglidingxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexperimentsveioxn--mgbpl2fhskypecoris-localhostcertificationxn--mgbqly7c0a67fbclever-clouderavpagexn--mgbqly7cvafricapooguyxn--mgbt3dhdxn--mgbtf8fldrvareservdxn--mgbtx2bielawalbrzycharternopilawalesundiscourses3-website-ap-northeast-1xn--mgbx4cd0abogadobeaemcloud-ip-dynamica-west-1xn--mix082fbxoschulplattforminamimakis-a-catererxn--mix891fedjeepharmacienschulserverxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44cleverappsaogoncanva-appsaotomelbournexn--mkru45is-lostrolekamakurazakiwielunnerxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-not-axn--mosjen-eyatsukanoyaizuwakamatsubushikusakadogawaxn--mot-tlavangenxn--mre-og-romsdal-qqbuservebolturindalxn--msy-ula0haiphongolffanshimosuwalkis-a-designerxn--mtta-vrjjat-k7aflakstadotsurugimbiella-speziaxarnetbankanzakiyosatokorozawaustevollpagest-mon-blogueurovision-ranchernigovernmentdllivingitpagemprendeatnuh-ohtawaramotoineppueblockbusterniizaustrheimdbambinagisobetsucks3-ap-southeast-2xn--muost-0qaxn--mxtq1miuraxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4dbrk0cexn--nit225kosakaerodromegalloabatobamaceratabusebastopoleangaviikafjordxn--nmesjevuemie-tcbalsan-sudtirolkuszczytnord-fron-riopretodayxn--nnx388axn--nodeloittexn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsicilyxn--o3cw4hair-surveillancexn--o3cyx2axn--od0algardxn--od0aq3bielskoczoweddinglitcheap-south-2xn--ogbpf8flekkefjordxn--oppegrd-ixaxn--ostery-fyatsushiroxn--osyro-wuaxn--otu796dxn--p1acfolksvelvikonskowolayangroupippugliaxn--p1ais-not-certifiedxn--pgbs0dhakatanortonkotsumomodenakatsugawaxn--porsgu-sta26fedorainfracloudfunctionschwarzgwesteuropencraftransfer-webappharmacyou2-localplayerxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4clickrisinglesjaguarvodkagaminombrendlyngenebakkeshibukawakeliwebhostingouv0xn--qcka1pmcprequalifymeinforumzxn--qqqt11miyazure-mobilevangerxn--qxa6axn--qxamiyotamanoxn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-savedxn--rennesy-v1axn--rhkkervju-01afedorapeopleikangerxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturbruksgymnxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byawaraxn--rny31hakodatexn--rovu88bieszczadygeyachimataijinderoyusuharazurefdietateshinanomachintaifun-dnsaliases121xn--rros-granvindafjordxn--rskog-uuaxn--rst-0navigationxn--rsta-framercanvasvn-repospeedpartnerxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawatahamaxn--s-1faitheshopwarezzoxn--s9brj9clientoyotsukaidownloadurbanamexnetfylkesbiblackbaudcdn-edgestackhero-networkinggroupperxn--sandnessjen-ogbizxn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphicswidnicaobangxn--skierv-utazurecontainerimamateramombetsupplieswidnikitagatamayukuhashimokitayamaxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navoizumizakis-slickharkivallee-aosteroyxn--slt-elabievathletajimabaria-vungtaudiopsys3-website-ap-southeast-1xn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbifukagawalmartaxiijimarugame-hostrowieconomiasagaeroclubmedecin-berlindasdaeguambulancechireadmyblogsytecnologiazurestaticappspaceusercontentproxy9guacuiababia-goraclecloudappschaefflereggiocalabriaurland-4-salernooreggioemiliaromagnarusawaurskog-holandinggff5xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbigv-infolldalomoldegreeu-central-2xn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bookkeepermashikexn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbiharvanedgeappengineu-south-1xn--stre-toten-zcbihoronobeokayamagasakikuchikuseihicampinashikiminohostfoldiscoverbaniazurewebsitests3-external-1xn--t60b56axn--tckwebview-assetswiebodzindependent-commissionxn--tiq49xqyjelenia-goraxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbikedaejeonbuk0emmafann-arborlandd-dnsfor-better-thanhhoarairkitapps-audiblebesbyencowayokosukanraetnaamesjevuemielnogiehtavuoatnabudejjuniper2-ddnss3-123minsidaarborteamsterdamnserverseating-organicbcg123homepagexl-o-g-i-navyokote123hjemmesidealerdalaheadjuegoshikibichuo0o0g0xn--trentin-sdtirol-7vbiomutazas3-website-ap-southeast-2xn--trentino-sd-tirol-c3birkenesoddtangentapps3-website-eu-west-1xn--trentino-sdtirol-szbittermezproxyusuitatamotors3-website-sa-east-1xn--trentinosd-tirol-rzbjarkoyuullensvanguardisharparisor-fronishiharaxn--trentinosdtirol-7vbjerkreimmobilieniwaizumiotsukumiyamazonaws-cloud9xn--trentinsd-tirol-6vbjugnieznorddalomzaporizhzhiaxn--trentinsdtirol-nsblackfridaynightayninhaccalvinklein-butterepairbusanagochigasakindigenakayamarumorimachidaxn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvarggatromsakegawaxn--uc0ay4axn--uist22hakonexn--uisz3gxn--unjrga-rtashkenturystykanmakiyokawaraxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtuscanyxn--valle-d-aoste-ehboehringerikerxn--valleaoste-e7axn--valledaoste-ebbvaapstempurlxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbloombergentingliwiceu-south-2xn--vestvgy-ixa6oxn--vg-yiablushangrilaakesvuemieleccevervaultgoryuzawaxn--vgan-qoaxn--vgsy-qoa0j0xn--vgu402clinicarbonia-iglesias-carboniaiglesiascarboniaxn--vhquvaroyxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bmoattachments3-website-us-east-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1cliniquenoharaxn--wgbl6axn--xhq521bms3-website-us-gov-west-1xn--xkc2al3hye2axn--xkc2dl3a5ee0hakubaclieu-1xn--y9a3aquarelleborkangerxn--yer-znavuotnarashinoharaxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4gbriminiserverxn--ystre-slidre-ujbmwcloudnonproddaemongolianishiizunazukindustriaxn--zbx025dxn--zf0avxn--4it168dxn--zfr164bnrweatherchannelsdvrdns3-website-us-west-1xnbayernxz
\ No newline at end of file
diff --git a/publicsuffix/example_test.go b/publicsuffix/example_test.go
index c051dac8e0..771ec4e4ec 100644
--- a/publicsuffix/example_test.go
+++ b/publicsuffix/example_test.go
@@ -42,7 +42,6 @@ func ExamplePublicSuffix_manager() {
"foo.org",
"foo.co.uk",
"foo.dyndns.org",
- "foo.blogspot.co.uk",
"cromulent",
}
@@ -88,6 +87,5 @@ func ExamplePublicSuffix_manager() {
// > foo.org org is ICANN Managed
// > foo.co.uk co.uk is ICANN Managed
// > foo.dyndns.org dyndns.org is Privately Managed
- // > foo.blogspot.co.uk blogspot.co.uk is Privately Managed
// > cromulent cromulent is Unmanaged
}
diff --git a/publicsuffix/gen.go b/publicsuffix/gen.go
index 7f7d08dbc2..5f454e57e9 100644
--- a/publicsuffix/gen.go
+++ b/publicsuffix/gen.go
@@ -21,6 +21,7 @@ package main
import (
"bufio"
"bytes"
+ "cmp"
"encoding/binary"
"flag"
"fmt"
@@ -29,7 +30,7 @@ import (
"net/http"
"os"
"regexp"
- "sort"
+ "slices"
"strings"
"golang.org/x/net/idna"
@@ -62,20 +63,6 @@ var (
maxLo uint32
)
-func max(a, b int) int {
- if a < b {
- return b
- }
- return a
-}
-
-func u32max(a, b uint32) uint32 {
- if a < b {
- return b
- }
- return a
-}
-
const (
nodeTypeNormal = 0
nodeTypeException = 1
@@ -83,18 +70,6 @@ const (
numNodeType = 3
)
-func nodeTypeStr(n int) string {
- switch n {
- case nodeTypeNormal:
- return "+"
- case nodeTypeException:
- return "!"
- case nodeTypeParentOnly:
- return "o"
- }
- panic("unreachable")
-}
-
const (
defaultURL = "https://publicsuffix.org/list/effective_tld_names.dat"
gitCommitURL = "https://api.github.com/repos/publicsuffix/list/commits?path=public_suffix_list.dat"
@@ -251,7 +226,7 @@ func main1() error {
for label := range labelsMap {
labelsList = append(labelsList, label)
}
- sort.Strings(labelsList)
+ slices.Sort(labelsList)
combinedText = combineText(labelsList)
if combinedText == "" {
@@ -509,15 +484,13 @@ func (n *node) child(label string) *node {
icann: true,
}
n.children = append(n.children, c)
- sort.Sort(byLabel(n.children))
+ slices.SortFunc(n.children, byLabel)
return c
}
-type byLabel []*node
-
-func (b byLabel) Len() int { return len(b) }
-func (b byLabel) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
-func (b byLabel) Less(i, j int) bool { return b[i].label < b[j].label }
+func byLabel(a, b *node) int {
+ return strings.Compare(a.label, b.label)
+}
var nextNodesIndex int
@@ -557,7 +530,7 @@ func assignIndexes(n *node) error {
n.childrenIndex = len(childrenEncoding)
lo := uint32(n.firstChild)
hi := lo + uint32(len(n.children))
- maxLo, maxHi = u32max(maxLo, lo), u32max(maxHi, hi)
+ maxLo, maxHi = max(maxLo, lo), max(maxHi, hi)
if lo >= 1< 0 && ss[0] == "" {
ss = ss[1:]
}
diff --git a/publicsuffix/list.go b/publicsuffix/list.go
index d56e9e7624..047cb30eb1 100644
--- a/publicsuffix/list.go
+++ b/publicsuffix/list.go
@@ -77,7 +77,7 @@ func (list) String() string {
// privately managed domain (and in practice, not a top level domain) or an
// unmanaged top level domain (and not explicitly mentioned in the
// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN
-// domains, "foo.dyndns.org" and "foo.blogspot.co.uk" are private domains and
+// domains, "foo.dyndns.org" is a private domain and
// "cromulent" is an unmanaged top level domain.
//
// Use cases for distinguishing ICANN domains like "foo.com" from private
@@ -88,7 +88,7 @@ func PublicSuffix(domain string) (publicSuffix string, icann bool) {
s, suffix, icannNode, wildcard := domain, len(domain), false, false
loop:
for {
- dot := strings.LastIndex(s, ".")
+ dot := strings.LastIndexByte(s, '.')
if wildcard {
icann = icannNode
suffix = 1 + dot
@@ -129,7 +129,7 @@ loop:
}
if suffix == len(domain) {
// If no rules match, the prevailing rule is "*".
- return domain[1+strings.LastIndex(domain, "."):], icann
+ return domain[1+strings.LastIndexByte(domain, '.'):], icann
}
return domain[suffix:], icann
}
@@ -178,26 +178,28 @@ func EffectiveTLDPlusOne(domain string) (string, error) {
if domain[i] != '.' {
return "", fmt.Errorf("publicsuffix: invalid public suffix %q for domain %q", suffix, domain)
}
- return domain[1+strings.LastIndex(domain[:i], "."):], nil
+ return domain[1+strings.LastIndexByte(domain[:i], '.'):], nil
}
type uint32String string
func (u uint32String) get(i uint32) uint32 {
off := i * 4
- return (uint32(u[off])<<24 |
- uint32(u[off+1])<<16 |
- uint32(u[off+2])<<8 |
- uint32(u[off+3]))
+ u = u[off:] // help the compiler reduce bounds checks
+ return uint32(u[3]) |
+ uint32(u[2])<<8 |
+ uint32(u[1])<<16 |
+ uint32(u[0])<<24
}
type uint40String string
func (u uint40String) get(i uint32) uint64 {
off := uint64(i * (nodesBits / 8))
- return uint64(u[off])<<32 |
- uint64(u[off+1])<<24 |
- uint64(u[off+2])<<16 |
- uint64(u[off+3])<<8 |
- uint64(u[off+4])
+ u = u[off:] // help the compiler reduce bounds checks
+ return uint64(u[4]) |
+ uint64(u[3])<<8 |
+ uint64(u[2])<<16 |
+ uint64(u[1])<<24 |
+ uint64(u[0])<<32
}
diff --git a/publicsuffix/list_test.go b/publicsuffix/list_test.go
index 090c431139..7a1bb0fe5c 100644
--- a/publicsuffix/list_test.go
+++ b/publicsuffix/list_test.go
@@ -63,12 +63,11 @@ func TestFind(t *testing.T) {
func TestICANN(t *testing.T) {
testCases := map[string]bool{
- "foo.org": true,
- "foo.co.uk": true,
- "foo.dyndns.org": false,
- "foo.go.dyndns.org": false,
- "foo.blogspot.co.uk": false,
- "foo.intranet": false,
+ "foo.org": true,
+ "foo.co.uk": true,
+ "foo.dyndns.org": false,
+ "foo.go.dyndns.org": false,
+ "foo.intranet": false,
}
for domain, want := range testCases {
_, got := PublicSuffix(domain)
@@ -111,16 +110,12 @@ var publicSuffixTestCases = []struct {
// net.ar
// org.ar
// tur.ar
- // blogspot.com.ar (in the PRIVATE DOMAIN section).
{"ar", "ar", true},
{"www.ar", "ar", true},
{"nic.ar", "ar", true},
{"www.nic.ar", "ar", true},
{"com.ar", "com.ar", true},
{"www.com.ar", "com.ar", true},
- {"blogspot.com.ar", "blogspot.com.ar", false}, // PRIVATE DOMAIN.
- {"www.blogspot.com.ar", "blogspot.com.ar", false}, // PRIVATE DOMAIN.
- {"www.xxx.yyy.zzz.blogspot.com.ar", "blogspot.com.ar", false}, // PRIVATE DOMAIN.
{"logspot.com.ar", "com.ar", true},
{"zlogspot.com.ar", "com.ar", true},
{"zblogspot.com.ar", "com.ar", true},
@@ -170,20 +165,13 @@ var publicSuffixTestCases = []struct {
// game.tw
// ebiz.tw
// club.tw
- // 網路.tw (xn--zf0ao64a.tw)
- // 組織.tw (xn--uc0atv.tw)
- // 商業.tw (xn--czrw28b.tw)
- // blogspot.tw
+ // 台灣.tw (xn--kpry57d.tw)
{"tw", "tw", true},
{"aaa.tw", "tw", true},
{"www.aaa.tw", "tw", true},
{"xn--czrw28b.aaa.tw", "tw", true},
{"edu.tw", "edu.tw", true},
{"www.edu.tw", "edu.tw", true},
- {"xn--czrw28b.edu.tw", "edu.tw", true},
- {"xn--czrw28b.tw", "xn--czrw28b.tw", true},
- {"www.xn--czrw28b.tw", "xn--czrw28b.tw", true},
- {"xn--uc0atv.xn--czrw28b.tw", "xn--czrw28b.tw", true},
{"xn--kpry57d.tw", "tw", true},
// The .uk rules are:
@@ -199,7 +187,6 @@ var publicSuffixTestCases = []struct {
// plc.uk
// police.uk
// *.sch.uk
- // blogspot.co.uk (in the PRIVATE DOMAIN section).
{"uk", "uk", true},
{"aaa.uk", "uk", true},
{"www.aaa.uk", "uk", true},
@@ -210,9 +197,6 @@ var publicSuffixTestCases = []struct {
{"www.sch.uk", "www.sch.uk", true},
{"co.uk", "co.uk", true},
{"www.co.uk", "co.uk", true},
- {"blogspot.co.uk", "blogspot.co.uk", false}, // PRIVATE DOMAIN.
- {"blogspot.nic.uk", "uk", true},
- {"blogspot.sch.uk", "blogspot.sch.uk", true},
// The .рф rules are
// рф (xn--p1ai)
@@ -322,10 +306,10 @@ func TestNumICANNRules(t *testing.T) {
// Check the last ICANN and first Private rules. If the underlying public
// suffix list changes, we may need to update these hard-coded checks.
if got, want := rules[numICANNRules-1], "zuerich"; got != want {
- t.Errorf("last ICANN rule: got %q, wawnt %q", got, want)
+ t.Errorf("last ICANN rule: got %q, want %q", got, want)
}
- if got, want := rules[numICANNRules], "cc.ua"; got != want {
- t.Errorf("first Private rule: got %q, wawnt %q", got, want)
+ if got, want := rules[numICANNRules], "co.krd"; got != want {
+ t.Errorf("first Private rule: got %q, want %q", got, want)
}
}
diff --git a/publicsuffix/table.go b/publicsuffix/table.go
index 78d400fa65..0fadf9527f 100644
--- a/publicsuffix/table.go
+++ b/publicsuffix/table.go
@@ -4,7 +4,7 @@ package publicsuffix
import _ "embed"
-const version = "publicsuffix.org's public_suffix_list.dat, git revision 63cbc63d470d7b52c35266aa96c4c98c96ec499c (2023-08-03T10:01:25Z)"
+const version = "publicsuffix.org's public_suffix_list.dat, git revision 2c960dac3d39ba521eb5db9da192968f5be0aded (2025-03-18T07:22:13Z)"
const (
nodesBits = 40
@@ -26,7 +26,7 @@ const (
)
// numTLD is the number of top level domains.
-const numTLD = 1474
+const numTLD = 1454
// text is the combined text of all labels.
//
@@ -63,8 +63,8 @@ var nodes uint40String
//go:embed data/children
var children uint32String
-// max children 743 (capacity 1023)
-// max text offset 30876 (capacity 65535)
+// max children 870 (capacity 1023)
+// max text offset 31785 (capacity 65535)
// max text length 31 (capacity 63)
-// max hi 9322 (capacity 16383)
-// max lo 9317 (capacity 16383)
+// max hi 10100 (capacity 16383)
+// max lo 10095 (capacity 16383)
diff --git a/publicsuffix/table_test.go b/publicsuffix/table_test.go
index a297b3b0dd..247e695a88 100644
--- a/publicsuffix/table_test.go
+++ b/publicsuffix/table_test.go
@@ -2,41 +2,39 @@
package publicsuffix
-const numICANNRules = 6893
+const numICANNRules = 6871
var rules = [...]string{
"ac",
"com.ac",
"edu.ac",
"gov.ac",
- "net.ac",
"mil.ac",
+ "net.ac",
"org.ac",
"ad",
- "nom.ad",
"ae",
+ "ac.ae",
"co.ae",
+ "gov.ae",
+ "mil.ae",
"net.ae",
"org.ae",
"sch.ae",
- "ac.ae",
- "gov.ae",
- "mil.ae",
"aero",
+ "airline.aero",
+ "airport.aero",
"accident-investigation.aero",
"accident-prevention.aero",
"aerobatic.aero",
"aeroclub.aero",
"aerodrome.aero",
"agents.aero",
- "aircraft.aero",
- "airline.aero",
- "airport.aero",
"air-surveillance.aero",
- "airtraffic.aero",
"air-traffic-control.aero",
+ "aircraft.aero",
+ "airtraffic.aero",
"ambulance.aero",
- "amusement.aero",
"association.aero",
"author.aero",
"ballooning.aero",
@@ -67,6 +65,7 @@ var rules = [...]string{
"express.aero",
"federation.aero",
"flight.aero",
+ "freight.aero",
"fuel.aero",
"gliding.aero",
"government.aero",
@@ -81,6 +80,7 @@ var rules = [...]string{
"logistics.aero",
"magazine.aero",
"maintenance.aero",
+ "marketplace.aero",
"media.aero",
"microlight.aero",
"modelling.aero",
@@ -103,6 +103,7 @@ var rules = [...]string{
"skydiving.aero",
"software.aero",
"student.aero",
+ "taxi.aero",
"trader.aero",
"trading.aero",
"trainer.aero",
@@ -110,21 +111,21 @@ var rules = [...]string{
"workinggroup.aero",
"works.aero",
"af",
- "gov.af",
"com.af",
- "org.af",
- "net.af",
"edu.af",
+ "gov.af",
+ "net.af",
+ "org.af",
"ag",
+ "co.ag",
"com.ag",
- "org.ag",
"net.ag",
- "co.ag",
"nom.ag",
+ "org.ag",
"ai",
- "off.ai",
"com.ai",
"net.ai",
+ "off.ai",
"org.ai",
"al",
"com.al",
@@ -140,12 +141,15 @@ var rules = [...]string{
"net.am",
"org.am",
"ao",
+ "co.ao",
"ed.ao",
+ "edu.ao",
+ "gov.ao",
"gv.ao",
+ "it.ao",
"og.ao",
- "co.ao",
+ "org.ao",
"pb.ao",
- "it.ao",
"aq",
"ar",
"bet.ar",
@@ -164,6 +168,7 @@ var rules = [...]string{
"tur.ar",
"arpa",
"e164.arpa",
+ "home.arpa",
"in-addr.arpa",
"ip6.arpa",
"iris.arpa",
@@ -174,19 +179,18 @@ var rules = [...]string{
"asia",
"at",
"ac.at",
+ "sth.ac.at",
"co.at",
"gv.at",
"or.at",
- "sth.ac.at",
"au",
+ "asn.au",
"com.au",
- "net.au",
- "org.au",
"edu.au",
"gov.au",
- "asn.au",
"id.au",
- "info.au",
+ "net.au",
+ "org.au",
"conf.au",
"oz.au",
"act.au",
@@ -216,18 +220,19 @@ var rules = [...]string{
"com.aw",
"ax",
"az",
+ "biz.az",
+ "co.az",
"com.az",
- "net.az",
- "int.az",
- "gov.az",
- "org.az",
"edu.az",
+ "gov.az",
"info.az",
- "pp.az",
+ "int.az",
"mil.az",
"name.az",
+ "net.az",
+ "org.az",
+ "pp.az",
"pro.az",
- "biz.az",
"ba",
"com.ba",
"edu.ba",
@@ -252,6 +257,16 @@ var rules = [...]string{
"bf",
"gov.bf",
"bg",
+ "0.bg",
+ "1.bg",
+ "2.bg",
+ "3.bg",
+ "4.bg",
+ "5.bg",
+ "6.bg",
+ "7.bg",
+ "8.bg",
+ "9.bg",
"a.bg",
"b.bg",
"c.bg",
@@ -278,22 +293,12 @@ var rules = [...]string{
"x.bg",
"y.bg",
"z.bg",
- "0.bg",
- "1.bg",
- "2.bg",
- "3.bg",
- "4.bg",
- "5.bg",
- "6.bg",
- "7.bg",
- "8.bg",
- "9.bg",
"bh",
"com.bh",
"edu.bh",
+ "gov.bh",
"net.bh",
"org.bh",
- "gov.bh",
"bi",
"co.bi",
"com.bi",
@@ -318,8 +323,8 @@ var rules = [...]string{
"net.bj",
"org.bj",
"ote.bj",
- "resto.bj",
"restaurant.bj",
+ "resto.bj",
"tourism.bj",
"univ.bj",
"bm",
@@ -339,9 +344,9 @@ var rules = [...]string{
"edu.bo",
"gob.bo",
"int.bo",
- "org.bo",
- "net.bo",
"mil.bo",
+ "net.bo",
+ "org.bo",
"tv.bo",
"web.bo",
"academia.bo",
@@ -366,9 +371,9 @@ var rules = [...]string{
"nombre.bo",
"noticias.bo",
"patria.bo",
+ "plurinacional.bo",
"politica.bo",
"profesional.bo",
- "plurinacional.bo",
"pueblo.bo",
"revista.bo",
"salud.bo",
@@ -393,6 +398,7 @@ var rules = [...]string{
"b.br",
"barueri.br",
"belem.br",
+ "bet.br",
"bhz.br",
"bib.br",
"bio.br",
@@ -479,6 +485,7 @@ var rules = [...]string{
"jor.br",
"jus.br",
"leg.br",
+ "leilao.br",
"lel.br",
"log.br",
"londrina.br",
@@ -547,10 +554,10 @@ var rules = [...]string{
"zlg.br",
"bs",
"com.bs",
- "net.bs",
- "org.bs",
"edu.bs",
"gov.bs",
+ "net.bs",
+ "org.bs",
"bt",
"com.bt",
"edu.bt",
@@ -559,7 +566,10 @@ var rules = [...]string{
"org.bt",
"bv",
"bw",
+ "ac.bw",
"co.bw",
+ "gov.bw",
+ "net.bw",
"org.bw",
"by",
"gov.by",
@@ -567,11 +577,12 @@ var rules = [...]string{
"com.by",
"of.by",
"bz",
+ "co.bz",
"com.bz",
- "net.bz",
- "org.bz",
"edu.bz",
"gov.bz",
+ "net.bz",
+ "org.bz",
"ca",
"ab.ca",
"bc.ca",
@@ -596,21 +607,19 @@ var rules = [...]string{
"cg",
"ch",
"ci",
- "org.ci",
- "or.ci",
- "com.ci",
+ "ac.ci",
+ "xn--aroport-bya.ci",
+ "asso.ci",
"co.ci",
- "edu.ci",
+ "com.ci",
"ed.ci",
- "ac.ci",
- "net.ci",
+ "edu.ci",
"go.ci",
- "asso.ci",
- "xn--aroport-bya.ci",
- "int.ci",
- "presse.ci",
- "md.ci",
"gouv.ci",
+ "int.ci",
+ "net.ci",
+ "or.ci",
+ "org.ci",
"*.ck",
"!www.ck",
"cl",
@@ -628,30 +637,32 @@ var rules = [...]string{
"com.cn",
"edu.cn",
"gov.cn",
+ "mil.cn",
"net.cn",
"org.cn",
- "mil.cn",
"xn--55qx5d.cn",
- "xn--io0a7i.cn",
"xn--od0alg.cn",
+ "xn--io0a7i.cn",
"ah.cn",
"bj.cn",
"cq.cn",
"fj.cn",
"gd.cn",
"gs.cn",
- "gz.cn",
"gx.cn",
+ "gz.cn",
"ha.cn",
"hb.cn",
"he.cn",
"hi.cn",
+ "hk.cn",
"hl.cn",
"hn.cn",
"jl.cn",
"js.cn",
"jx.cn",
"ln.cn",
+ "mo.cn",
"nm.cn",
"nx.cn",
"qh.cn",
@@ -661,27 +672,19 @@ var rules = [...]string{
"sn.cn",
"sx.cn",
"tj.cn",
+ "tw.cn",
"xj.cn",
"xz.cn",
"yn.cn",
"zj.cn",
- "hk.cn",
- "mo.cn",
- "tw.cn",
"co",
- "arts.co",
"com.co",
"edu.co",
- "firm.co",
"gov.co",
- "info.co",
- "int.co",
"mil.co",
"net.co",
"nom.co",
"org.co",
- "rec.co",
- "web.co",
"com",
"coop",
"cr",
@@ -695,16 +698,20 @@ var rules = [...]string{
"cu",
"com.cu",
"edu.cu",
- "org.cu",
- "net.cu",
- "gov.cu",
+ "gob.cu",
"inf.cu",
+ "nat.cu",
+ "net.cu",
+ "org.cu",
"cv",
"com.cv",
"edu.cv",
+ "id.cv",
"int.cv",
+ "net.cv",
"nome.cv",
"org.cv",
+ "publ.cv",
"cw",
"com.cw",
"edu.cw",
@@ -730,11 +737,12 @@ var rules = [...]string{
"dj",
"dk",
"dm",
+ "co.dm",
"com.dm",
- "net.dm",
- "org.dm",
"edu.dm",
"gov.dm",
+ "net.dm",
+ "org.dm",
"do",
"art.do",
"com.do",
@@ -752,62 +760,67 @@ var rules = [...]string{
"com.dz",
"edu.dz",
"gov.dz",
- "org.dz",
"net.dz",
+ "org.dz",
"pol.dz",
"soc.dz",
"tm.dz",
"ec",
"com.ec",
- "info.ec",
- "net.ec",
+ "edu.ec",
"fin.ec",
+ "gob.ec",
+ "gov.ec",
+ "info.ec",
"k12.ec",
"med.ec",
- "pro.ec",
- "org.ec",
- "edu.ec",
- "gov.ec",
- "gob.ec",
"mil.ec",
+ "net.ec",
+ "org.ec",
+ "pro.ec",
"edu",
"ee",
+ "aip.ee",
+ "com.ee",
"edu.ee",
+ "fie.ee",
"gov.ee",
- "riik.ee",
"lib.ee",
"med.ee",
- "com.ee",
- "pri.ee",
- "aip.ee",
"org.ee",
- "fie.ee",
+ "pri.ee",
+ "riik.ee",
"eg",
+ "ac.eg",
"com.eg",
"edu.eg",
"eun.eg",
"gov.eg",
+ "info.eg",
+ "me.eg",
"mil.eg",
"name.eg",
"net.eg",
"org.eg",
"sci.eg",
+ "sport.eg",
+ "tv.eg",
"*.er",
"es",
"com.es",
+ "edu.es",
+ "gob.es",
"nom.es",
"org.es",
- "gob.es",
- "edu.es",
"et",
+ "biz.et",
"com.et",
- "gov.et",
- "org.et",
"edu.et",
- "biz.et",
- "name.et",
+ "gov.et",
"info.et",
+ "name.et",
"net.et",
+ "org.et",
"eu",
"fi",
"aland.fi",
@@ -823,11 +836,11 @@ var rules = [...]string{
"org.fj",
"pro.fj",
"*.fk",
+ "fm",
"com.fm",
"edu.fm",
"net.fm",
"org.fm",
- "fm",
"fo",
"fr",
"asso.fr",
@@ -836,34 +849,23 @@ var rules = [...]string{
"nom.fr",
"prd.fr",
"tm.fr",
- "aeroport.fr",
- "avocat.fr",
"avoues.fr",
"cci.fr",
- "chambagri.fr",
- "chirurgiens-dentistes.fr",
- "experts-comptables.fr",
- "geometre-expert.fr",
"greta.fr",
"huissier-justice.fr",
- "medecin.fr",
- "notaires.fr",
- "pharmacien.fr",
- "port.fr",
- "veterinaire.fr",
"ga",
"gb",
+ "gd",
"edu.gd",
"gov.gd",
- "gd",
"ge",
"com.ge",
"edu.ge",
"gov.ge",
- "org.ge",
- "mil.ge",
"net.ge",
+ "org.ge",
"pvt.ge",
+ "school.ge",
"gf",
"gg",
"co.gg",
@@ -873,14 +875,14 @@ var rules = [...]string{
"com.gh",
"edu.gh",
"gov.gh",
- "org.gh",
"mil.gh",
+ "org.gh",
"gi",
"com.gi",
- "ltd.gi",
+ "edu.gi",
"gov.gi",
+ "ltd.gi",
"mod.gi",
- "edu.gi",
"org.gi",
"gl",
"co.gl",
@@ -894,23 +896,23 @@ var rules = [...]string{
"com.gn",
"edu.gn",
"gov.gn",
- "org.gn",
"net.gn",
+ "org.gn",
"gov",
"gp",
+ "asso.gp",
"com.gp",
- "net.gp",
- "mobi.gp",
"edu.gp",
+ "mobi.gp",
+ "net.gp",
"org.gp",
- "asso.gp",
"gq",
"gr",
"com.gr",
"edu.gr",
+ "gov.gr",
"net.gr",
"org.gr",
- "gov.gr",
"gs",
"gt",
"com.gt",
@@ -944,81 +946,81 @@ var rules = [...]string{
"idv.hk",
"net.hk",
"org.hk",
+ "xn--ciqpn.hk",
+ "xn--gmqw5a.hk",
"xn--55qx5d.hk",
- "xn--wcvs22d.hk",
- "xn--lcvr32d.hk",
"xn--mxtq1m.hk",
- "xn--gmqw5a.hk",
- "xn--ciqpn.hk",
+ "xn--lcvr32d.hk",
+ "xn--wcvs22d.hk",
"xn--gmq050i.hk",
+ "xn--uc0atv.hk",
+ "xn--uc0ay4a.hk",
+ "xn--od0alg.hk",
"xn--zf0avx.hk",
- "xn--io0a7i.hk",
"xn--mk0axi.hk",
- "xn--od0alg.hk",
- "xn--od0aq3b.hk",
"xn--tn0ag.hk",
- "xn--uc0atv.hk",
- "xn--uc0ay4a.hk",
+ "xn--od0aq3b.hk",
+ "xn--io0a7i.hk",
"hm",
"hn",
"com.hn",
"edu.hn",
- "org.hn",
- "net.hn",
- "mil.hn",
"gob.hn",
+ "mil.hn",
+ "net.hn",
+ "org.hn",
"hr",
- "iz.hr",
+ "com.hr",
"from.hr",
+ "iz.hr",
"name.hr",
- "com.hr",
"ht",
+ "adult.ht",
+ "art.ht",
+ "asso.ht",
"com.ht",
- "shop.ht",
+ "coop.ht",
+ "edu.ht",
"firm.ht",
+ "gouv.ht",
"info.ht",
- "adult.ht",
+ "med.ht",
"net.ht",
- "pro.ht",
"org.ht",
- "med.ht",
- "art.ht",
- "coop.ht",
+ "perso.ht",
"pol.ht",
- "asso.ht",
- "edu.ht",
+ "pro.ht",
"rel.ht",
- "gouv.ht",
- "perso.ht",
+ "shop.ht",
"hu",
- "co.hu",
- "info.hu",
- "org.hu",
- "priv.hu",
- "sport.hu",
- "tm.hu",
"2000.hu",
"agrar.hu",
"bolt.hu",
"casino.hu",
"city.hu",
+ "co.hu",
"erotica.hu",
"erotika.hu",
"film.hu",
"forum.hu",
"games.hu",
"hotel.hu",
+ "info.hu",
"ingatlan.hu",
"jogasz.hu",
"konyvelo.hu",
"lakas.hu",
"media.hu",
"news.hu",
+ "org.hu",
+ "priv.hu",
"reklam.hu",
"sex.hu",
"shop.hu",
+ "sport.hu",
"suli.hu",
"szex.hu",
+ "tm.hu",
"tozsde.hu",
"utazas.hu",
"video.hu",
@@ -1054,11 +1056,11 @@ var rules = [...]string{
"im",
"ac.im",
"co.im",
- "com.im",
"ltd.co.im",
+ "plc.co.im",
+ "com.im",
"net.im",
"org.im",
- "plc.co.im",
"tt.im",
"tv.im",
"in",
@@ -1107,14 +1109,21 @@ var rules = [...]string{
"int",
"eu.int",
"io",
+ "co.io",
"com.io",
+ "edu.io",
+ "gov.io",
+ "mil.io",
+ "net.io",
+ "nom.io",
+ "org.io",
"iq",
- "gov.iq",
+ "com.iq",
"edu.iq",
+ "gov.iq",
"mil.iq",
- "com.iq",
- "org.iq",
"net.iq",
+ "org.iq",
"ir",
"ac.ir",
"co.ir",
@@ -1126,15 +1135,9 @@ var rules = [...]string{
"xn--mgba3a4f16a.ir",
"xn--mgba3a4fra.ir",
"is",
- "net.is",
- "com.is",
- "edu.is",
- "gov.is",
- "org.is",
- "int.is",
"it",
- "gov.it",
"edu.it",
+ "gov.it",
"abr.it",
"abruzzo.it",
"aosta-valley.it",
@@ -1193,6 +1196,7 @@ var rules = [...]string{
"xn--trentin-sdtirol-7vb.it",
"trentin-sued-tirol.it",
"trentin-suedtirol.it",
+ "trentino.it",
"trentino-a-adige.it",
"trentino-aadige.it",
"trentino-alto-adige.it",
@@ -1205,7 +1209,6 @@ var rules = [...]string{
"xn--trentino-sdtirol-szb.it",
"trentino-sued-tirol.it",
"trentino-suedtirol.it",
- "trentino.it",
"trentinoa-adige.it",
"trentinoaadige.it",
"trentinoalto-adige.it",
@@ -1276,10 +1279,10 @@ var rules = [...]string{
"av.it",
"avellino.it",
"ba.it",
+ "balsan.it",
"balsan-sudtirol.it",
"xn--balsan-sdtirol-nsb.it",
"balsan-suedtirol.it",
- "balsan.it",
"bari.it",
"barletta-trani-andria.it",
"barlettatraniandria.it",
@@ -1293,21 +1296,21 @@ var rules = [...]string{
"bn.it",
"bo.it",
"bologna.it",
- "bolzano-altoadige.it",
"bolzano.it",
+ "bolzano-altoadige.it",
+ "bozen.it",
"bozen-sudtirol.it",
"xn--bozen-sdtirol-2ob.it",
"bozen-suedtirol.it",
- "bozen.it",
"br.it",
"brescia.it",
"brindisi.it",
"bs.it",
"bt.it",
+ "bulsan.it",
"bulsan-sudtirol.it",
"xn--bulsan-sdtirol-nsb.it",
"bulsan-suedtirol.it",
- "bulsan.it",
"bz.it",
"ca.it",
"cagliari.it",
@@ -1409,9 +1412,9 @@ var rules = [...]string{
"mn.it",
"mo.it",
"modena.it",
+ "monza.it",
"monza-brianza.it",
"monza-e-della-brianza.it",
- "monza.it",
"monzabrianza.it",
"monzaebrianza.it",
"monzaedellabrianza.it",
@@ -1490,8 +1493,8 @@ var rules = [...]string{
"sp.it",
"sr.it",
"ss.it",
- "suedtirol.it",
"xn--sdtirol-n2a.it",
+ "suedtirol.it",
"sv.it",
"ta.it",
"taranto.it",
@@ -1545,14 +1548,20 @@ var rules = [...]string{
"org.je",
"*.jm",
"jo",
+ "agri.jo",
+ "ai.jo",
"com.jo",
- "org.jo",
- "net.jo",
"edu.jo",
- "sch.jo",
+ "eng.jo",
+ "fm.jo",
"gov.jo",
"mil.jo",
- "name.jo",
+ "net.jo",
+ "org.jo",
+ "per.jo",
+ "phd.jo",
+ "sch.jo",
+ "tv.jo",
"jobs",
"jp",
"ac.jp",
@@ -1611,26 +1620,14 @@ var rules = [...]string{
"yamagata.jp",
"yamaguchi.jp",
"yamanashi.jp",
- "xn--4pvxs.jp",
- "xn--vgu402c.jp",
- "xn--c3s14m.jp",
+ "xn--ehqz56n.jp",
+ "xn--1lqs03n.jp",
+ "xn--qqqt11m.jp",
"xn--f6qx53a.jp",
- "xn--8pvr4u.jp",
- "xn--uist22h.jp",
"xn--djrs72d6uy.jp",
"xn--mkru45i.jp",
"xn--0trq7p7nn.jp",
- "xn--8ltr62k.jp",
- "xn--2m4a15e.jp",
- "xn--efvn9s.jp",
- "xn--32vp30h.jp",
- "xn--4it797k.jp",
- "xn--1lqs71d.jp",
- "xn--5rtp49c.jp",
"xn--5js045d.jp",
- "xn--ehqz56n.jp",
- "xn--1lqs03n.jp",
- "xn--qqqt11m.jp",
"xn--kbrq7o.jp",
"xn--pssu33l.jp",
"xn--ntsq17g.jp",
@@ -1640,37 +1637,49 @@ var rules = [...]string{
"xn--6orx2r.jp",
"xn--rht61e.jp",
"xn--rht27z.jp",
- "xn--djty4k.jp",
"xn--nit225k.jp",
"xn--rht3d.jp",
+ "xn--djty4k.jp",
"xn--klty5x.jp",
"xn--kltx9a.jp",
"xn--kltp7d.jp",
+ "xn--c3s14m.jp",
+ "xn--vgu402c.jp",
+ "xn--efvn9s.jp",
+ "xn--1lqs71d.jp",
+ "xn--4pvxs.jp",
"xn--uuwu58a.jp",
"xn--zbx025d.jp",
+ "xn--8pvr4u.jp",
+ "xn--5rtp49c.jp",
"xn--ntso0iqx3a.jp",
"xn--elqq16h.jp",
"xn--4it168d.jp",
"xn--klt787d.jp",
"xn--rny31h.jp",
"xn--7t0a264c.jp",
+ "xn--uist22h.jp",
+ "xn--8ltr62k.jp",
+ "xn--2m4a15e.jp",
+ "xn--32vp30h.jp",
+ "xn--4it797k.jp",
"xn--5rtq34k.jp",
"xn--k7yn95e.jp",
"xn--tor131o.jp",
"xn--d5qv7z876c.jp",
"*.kawasaki.jp",
- "*.kitakyushu.jp",
- "*.kobe.jp",
- "*.nagoya.jp",
- "*.sapporo.jp",
- "*.sendai.jp",
- "*.yokohama.jp",
"!city.kawasaki.jp",
+ "*.kitakyushu.jp",
"!city.kitakyushu.jp",
+ "*.kobe.jp",
"!city.kobe.jp",
+ "*.nagoya.jp",
"!city.nagoya.jp",
+ "*.sapporo.jp",
"!city.sapporo.jp",
+ "*.sendai.jp",
"!city.sendai.jp",
+ "*.yokohama.jp",
"!city.yokohama.jp",
"aisai.aichi.jp",
"ama.aichi.jp",
@@ -3356,44 +3365,44 @@ var rules = [...]string{
"or.ke",
"sc.ke",
"kg",
- "org.kg",
- "net.kg",
"com.kg",
"edu.kg",
"gov.kg",
"mil.kg",
+ "net.kg",
+ "org.kg",
"*.kh",
"ki",
- "edu.ki",
"biz.ki",
- "net.ki",
- "org.ki",
+ "com.ki",
+ "edu.ki",
"gov.ki",
"info.ki",
- "com.ki",
+ "net.ki",
+ "org.ki",
"km",
- "org.km",
- "nom.km",
+ "ass.km",
+ "com.km",
+ "edu.km",
"gov.km",
+ "mil.km",
+ "nom.km",
+ "org.km",
"prd.km",
"tm.km",
- "edu.km",
- "mil.km",
- "ass.km",
- "com.km",
- "coop.km",
"asso.km",
- "presse.km",
+ "coop.km",
+ "gouv.km",
"medecin.km",
"notaires.km",
"pharmaciens.km",
+ "presse.km",
"veterinaire.km",
- "gouv.km",
"kn",
- "net.kn",
- "org.kn",
"edu.kn",
"gov.kn",
+ "net.kn",
+ "org.kn",
"kp",
"com.kp",
"edu.kp",
@@ -3403,11 +3412,15 @@ var rules = [...]string{
"tra.kp",
"kr",
"ac.kr",
+ "ai.kr",
"co.kr",
"es.kr",
"go.kr",
"hs.kr",
+ "io.kr",
+ "it.kr",
"kg.kr",
+ "me.kr",
"mil.kr",
"ms.kr",
"ne.kr",
@@ -3445,21 +3458,21 @@ var rules = [...]string{
"net.ky",
"org.ky",
"kz",
- "org.kz",
+ "com.kz",
"edu.kz",
- "net.kz",
"gov.kz",
"mil.kz",
- "com.kz",
+ "net.kz",
+ "org.kz",
"la",
- "int.la",
- "net.la",
- "info.la",
+ "com.la",
"edu.la",
"gov.la",
- "per.la",
- "com.la",
+ "info.la",
+ "int.la",
+ "net.la",
"org.la",
+ "per.la",
"lb",
"com.lb",
"edu.lb",
@@ -3467,35 +3480,35 @@ var rules = [...]string{
"net.lb",
"org.lb",
"lc",
- "com.lc",
- "net.lc",
"co.lc",
- "org.lc",
+ "com.lc",
"edu.lc",
"gov.lc",
+ "net.lc",
+ "org.lc",
"li",
"lk",
- "gov.lk",
- "sch.lk",
- "net.lk",
- "int.lk",
+ "ac.lk",
+ "assn.lk",
"com.lk",
- "org.lk",
"edu.lk",
+ "gov.lk",
+ "grp.lk",
+ "hotel.lk",
+ "int.lk",
+ "ltd.lk",
+ "net.lk",
"ngo.lk",
+ "org.lk",
+ "sch.lk",
"soc.lk",
"web.lk",
- "ltd.lk",
- "assn.lk",
- "grp.lk",
- "hotel.lk",
- "ac.lk",
"lr",
"com.lr",
"edu.lr",
"gov.lr",
- "org.lr",
"net.lr",
+ "org.lr",
"ls",
"ac.ls",
"biz.ls",
@@ -3510,84 +3523,89 @@ var rules = [...]string{
"gov.lt",
"lu",
"lv",
+ "asn.lv",
"com.lv",
+ "conf.lv",
"edu.lv",
"gov.lv",
- "org.lv",
- "mil.lv",
"id.lv",
+ "mil.lv",
"net.lv",
- "asn.lv",
- "conf.lv",
+ "org.lv",
"ly",
"com.ly",
- "net.ly",
- "gov.ly",
- "plc.ly",
"edu.ly",
- "sch.ly",
+ "gov.ly",
+ "id.ly",
"med.ly",
+ "net.ly",
"org.ly",
- "id.ly",
+ "plc.ly",
+ "sch.ly",
"ma",
+ "ac.ma",
"co.ma",
- "net.ma",
"gov.ma",
+ "net.ma",
"org.ma",
- "ac.ma",
"press.ma",
"mc",
- "tm.mc",
"asso.mc",
+ "tm.mc",
"md",
"me",
+ "ac.me",
"co.me",
- "net.me",
- "org.me",
"edu.me",
- "ac.me",
"gov.me",
"its.me",
+ "net.me",
+ "org.me",
"priv.me",
"mg",
- "org.mg",
- "nom.mg",
- "gov.mg",
- "prd.mg",
- "tm.mg",
+ "co.mg",
+ "com.mg",
"edu.mg",
+ "gov.mg",
"mil.mg",
- "com.mg",
- "co.mg",
+ "nom.mg",
+ "org.mg",
+ "prd.mg",
"mh",
"mil",
"mk",
"com.mk",
- "org.mk",
- "net.mk",
"edu.mk",
"gov.mk",
"inf.mk",
"name.mk",
+ "net.mk",
+ "org.mk",
"ml",
+ "ac.ml",
+ "art.ml",
+ "asso.ml",
"com.ml",
"edu.ml",
"gouv.ml",
"gov.ml",
+ "info.ml",
+ "inst.ml",
"net.ml",
"org.ml",
+ "pr.ml",
"presse.ml",
"*.mm",
"mn",
- "gov.mn",
"edu.mn",
+ "gov.mn",
"org.mn",
"mo",
"com.mo",
- "net.mo",
- "org.mo",
"edu.mo",
"gov.mo",
+ "net.mo",
+ "org.mo",
"mobi",
"mp",
"mq",
@@ -3605,13 +3623,13 @@ var rules = [...]string{
"net.mt",
"org.mt",
"mu",
- "com.mu",
- "net.mu",
- "org.mu",
- "gov.mu",
"ac.mu",
"co.mu",
+ "com.mu",
+ "gov.mu",
+ "net.mu",
"or.mu",
+ "org.mu",
"museum",
"mv",
"aero.mv",
@@ -3637,15 +3655,14 @@ var rules = [...]string{
"edu.mw",
"gov.mw",
"int.mw",
- "museum.mw",
"net.mw",
"org.mw",
"mx",
"com.mx",
- "org.mx",
- "gob.mx",
"edu.mx",
+ "gob.mx",
"net.mx",
+ "org.mx",
"my",
"biz.my",
"com.my",
@@ -3665,22 +3682,11 @@ var rules = [...]string{
"net.mz",
"org.mz",
"na",
- "info.na",
- "pro.na",
- "name.na",
- "school.na",
- "or.na",
- "dr.na",
- "us.na",
- "mx.na",
- "ca.na",
- "in.na",
- "cc.na",
- "tv.na",
- "ws.na",
- "mobi.na",
+ "alt.na",
"co.na",
"com.na",
+ "gov.na",
+ "net.na",
"org.na",
"name",
"nc",
@@ -3689,16 +3695,16 @@ var rules = [...]string{
"ne",
"net",
"nf",
- "com.nf",
- "net.nf",
- "per.nf",
- "rec.nf",
- "web.nf",
"arts.nf",
+ "com.nf",
"firm.nf",
"info.nf",
+ "net.nf",
"other.nf",
+ "per.nf",
+ "rec.nf",
"store.nf",
+ "web.nf",
"ng",
"com.ng",
"edu.ng",
@@ -3728,17 +3734,17 @@ var rules = [...]string{
"nl",
"no",
"fhs.no",
- "vgs.no",
- "fylkesbibl.no",
"folkebibl.no",
- "museum.no",
+ "fylkesbibl.no",
"idrett.no",
+ "museum.no",
"priv.no",
- "mil.no",
- "stat.no",
+ "vgs.no",
"dep.no",
- "kommune.no",
"herad.no",
+ "kommune.no",
+ "mil.no",
+ "stat.no",
"aa.no",
"ah.no",
"bu.no",
@@ -3786,10 +3792,10 @@ var rules = [...]string{
"algard.no",
"xn--lgrd-poac.no",
"arna.no",
- "brumunddal.no",
- "bryne.no",
"bronnoysund.no",
"xn--brnnysund-m8ac.no",
+ "brumunddal.no",
+ "bryne.no",
"drobak.no",
"xn--drbak-wua.no",
"egersund.no",
@@ -3832,27 +3838,32 @@ var rules = [...]string{
"tananger.no",
"tranby.no",
"vossevangen.no",
+ "aarborte.no",
+ "aejrie.no",
"afjord.no",
"xn--fjord-lra.no",
"agdenes.no",
+ "nes.akershus.no",
+ "aknoluokta.no",
+ "xn--koluokta-7ya57h.no",
"al.no",
"xn--l-1fa.no",
+ "alaheadju.no",
+ "xn--laheadju-7ya.no",
"alesund.no",
"xn--lesund-hua.no",
"alstahaug.no",
"alta.no",
"xn--lt-liac.no",
- "alaheadju.no",
- "xn--laheadju-7ya.no",
"alvdal.no",
"amli.no",
"xn--mli-tla.no",
"amot.no",
"xn--mot-tla.no",
+ "andasuolo.no",
"andebu.no",
"andoy.no",
"xn--andy-ira.no",
- "andasuolo.no",
"ardal.no",
"xn--rdal-poa.no",
"aremark.no",
@@ -3862,9 +3873,9 @@ var rules = [...]string{
"xn--seral-lra.no",
"asker.no",
"askim.no",
- "askvoll.no",
"askoy.no",
"xn--asky-ira.no",
+ "askvoll.no",
"asnes.no",
"xn--snes-poa.no",
"audnedaln.no",
@@ -3877,27 +3888,37 @@ var rules = [...]string{
"austrheim.no",
"averoy.no",
"xn--avery-yua.no",
- "balestrand.no",
- "ballangen.no",
+ "badaddja.no",
+ "xn--bdddj-mrabd.no",
+ "xn--brum-voa.no",
+ "bahcavuotna.no",
+ "xn--bhcavuotna-s4a.no",
+ "bahccavuotna.no",
+ "xn--bhccavuotna-k7a.no",
+ "baidar.no",
+ "xn--bidr-5nac.no",
+ "bajddar.no",
+ "xn--bjddar-pta.no",
"balat.no",
"xn--blt-elab.no",
+ "balestrand.no",
+ "ballangen.no",
"balsfjord.no",
- "bahccavuotna.no",
- "xn--bhccavuotna-k7a.no",
"bamble.no",
"bardu.no",
+ "barum.no",
+ "batsfjord.no",
+ "xn--btsfjord-9za.no",
+ "bearalvahki.no",
+ "xn--bearalvhki-y4a.no",
"beardu.no",
"beiarn.no",
- "bajddar.no",
- "xn--bjddar-pta.no",
- "baidar.no",
- "xn--bidr-5nac.no",
"berg.no",
"bergen.no",
"berlevag.no",
"xn--berlevg-jxa.no",
- "bearalvahki.no",
- "xn--bearalvhki-y4a.no",
+ "bievat.no",
+ "xn--bievt-0qa.no",
"bindal.no",
"birkenes.no",
"bjarkoy.no",
@@ -3906,36 +3927,32 @@ var rules = [...]string{
"bjugn.no",
"bodo.no",
"xn--bod-2na.no",
- "badaddja.no",
- "xn--bdddj-mrabd.no",
- "budejju.no",
"bokn.no",
+ "bomlo.no",
+ "xn--bmlo-gra.no",
"bremanger.no",
"bronnoy.no",
"xn--brnny-wuac.no",
+ "budejju.no",
+ "nes.buskerud.no",
"bygland.no",
"bykle.no",
- "barum.no",
- "xn--brum-voa.no",
- "bo.telemark.no",
- "xn--b-5ga.telemark.no",
- "bo.nordland.no",
- "xn--b-5ga.nordland.no",
- "bievat.no",
- "xn--bievt-0qa.no",
- "bomlo.no",
- "xn--bmlo-gra.no",
- "batsfjord.no",
- "xn--btsfjord-9za.no",
- "bahcavuotna.no",
- "xn--bhcavuotna-s4a.no",
+ "cahcesuolo.no",
+ "xn--hcesuolo-7ya35b.no",
+ "davvenjarga.no",
+ "xn--davvenjrga-y4a.no",
+ "davvesiida.no",
+ "deatnu.no",
+ "dielddanuorri.no",
+ "divtasvuodna.no",
+ "divttasvuotna.no",
+ "donna.no",
+ "xn--dnna-gra.no",
"dovre.no",
"drammen.no",
"drangedal.no",
"dyroy.no",
"xn--dyry-ira.no",
- "donna.no",
- "xn--dnna-gra.no",
"eid.no",
"eidfjord.no",
"eidsberg.no",
@@ -3947,14 +3964,12 @@ var rules = [...]string{
"engerdal.no",
"etne.no",
"etnedal.no",
- "evenes.no",
"evenassi.no",
"xn--eveni-0qa01ga.no",
+ "evenes.no",
"evje-og-hornnes.no",
"farsund.no",
"fauske.no",
- "fuossko.no",
- "fuoisku.no",
"fedje.no",
"fet.no",
"finnoy.no",
@@ -3962,33 +3977,40 @@ var rules = [...]string{
"fitjar.no",
"fjaler.no",
"fjell.no",
+ "fla.no",
+ "xn--fl-zia.no",
"flakstad.no",
"flatanger.no",
"flekkefjord.no",
"flesberg.no",
"flora.no",
- "fla.no",
- "xn--fl-zia.no",
"folldal.no",
+ "forde.no",
+ "xn--frde-gra.no",
"forsand.no",
"fosnes.no",
+ "xn--frna-woa.no",
+ "frana.no",
"frei.no",
"frogn.no",
"froland.no",
"frosta.no",
- "frana.no",
- "xn--frna-woa.no",
"froya.no",
"xn--frya-hra.no",
+ "fuoisku.no",
+ "fuossko.no",
"fusa.no",
"fyresdal.no",
- "forde.no",
- "xn--frde-gra.no",
+ "gaivuotna.no",
+ "xn--givuotna-8ya.no",
+ "galsa.no",
+ "xn--gls-elac.no",
"gamvik.no",
"gangaviika.no",
"xn--ggaviika-8ya47h.no",
"gaular.no",
"gausdal.no",
+ "giehtavuoatna.no",
"gildeskal.no",
"xn--gildeskl-g0a.no",
"giske.no",
@@ -4006,38 +4028,37 @@ var rules = [...]string{
"gratangen.no",
"grimstad.no",
"grong.no",
- "kraanghke.no",
- "xn--kranghke-b0a.no",
"grue.no",
"gulen.no",
+ "guovdageaidnu.no",
+ "ha.no",
+ "xn--h-2fa.no",
+ "habmer.no",
+ "xn--hbmer-xqa.no",
"hadsel.no",
+ "xn--hgebostad-g3a.no",
+ "hagebostad.no",
"halden.no",
"halsa.no",
"hamar.no",
"hamaroy.no",
- "habmer.no",
- "xn--hbmer-xqa.no",
- "hapmir.no",
- "xn--hpmir-xqa.no",
- "hammerfest.no",
"hammarfeasta.no",
"xn--hmmrfeasta-s4ac.no",
+ "hammerfest.no",
+ "hapmir.no",
+ "xn--hpmir-xqa.no",
"haram.no",
"hareid.no",
"harstad.no",
"hasvik.no",
- "aknoluokta.no",
- "xn--koluokta-7ya57h.no",
"hattfjelldal.no",
- "aarborte.no",
"haugesund.no",
+ "os.hedmark.no",
+ "valer.hedmark.no",
+ "xn--vler-qoa.hedmark.no",
"hemne.no",
"hemnes.no",
"hemsedal.no",
- "heroy.more-og-romsdal.no",
- "xn--hery-ira.xn--mre-og-romsdal-qqb.no",
- "heroy.nordland.no",
- "xn--hery-ira.nordland.no",
"hitra.no",
"hjartdal.no",
"hjelmeland.no",
@@ -4049,96 +4070,95 @@ var rules = [...]string{
"holmestrand.no",
"holtalen.no",
"xn--holtlen-hxa.no",
+ "os.hordaland.no",
"hornindal.no",
"horten.no",
- "hurdal.no",
- "hurum.no",
- "hvaler.no",
- "hyllestad.no",
- "hagebostad.no",
- "xn--hgebostad-g3a.no",
"hoyanger.no",
"xn--hyanger-q1a.no",
"hoylandet.no",
"xn--hylandet-54a.no",
- "ha.no",
- "xn--h-2fa.no",
+ "hurdal.no",
+ "hurum.no",
+ "hvaler.no",
+ "hyllestad.no",
"ibestad.no",
"inderoy.no",
"xn--indery-fya.no",
"iveland.no",
+ "ivgu.no",
"jevnaker.no",
- "jondal.no",
"jolster.no",
"xn--jlster-bya.no",
- "karasjok.no",
+ "jondal.no",
+ "kafjord.no",
+ "xn--kfjord-iua.no",
"karasjohka.no",
"xn--krjohka-hwab49j.no",
+ "karasjok.no",
"karlsoy.no",
- "galsa.no",
- "xn--gls-elac.no",
"karmoy.no",
"xn--karmy-yua.no",
"kautokeino.no",
- "guovdageaidnu.no",
- "klepp.no",
"klabu.no",
"xn--klbu-woa.no",
+ "klepp.no",
"kongsberg.no",
"kongsvinger.no",
+ "kraanghke.no",
+ "xn--kranghke-b0a.no",
"kragero.no",
"xn--krager-gya.no",
"kristiansand.no",
"kristiansund.no",
"krodsherad.no",
"xn--krdsherad-m8a.no",
+ "xn--kvfjord-nxa.no",
+ "xn--kvnangen-k0a.no",
+ "kvafjord.no",
"kvalsund.no",
- "rahkkeravju.no",
- "xn--rhkkervju-01af.no",
"kvam.no",
+ "kvanangen.no",
"kvinesdal.no",
"kvinnherad.no",
"kviteseid.no",
"kvitsoy.no",
"xn--kvitsy-fya.no",
- "kvafjord.no",
- "xn--kvfjord-nxa.no",
- "giehtavuoatna.no",
- "kvanangen.no",
- "xn--kvnangen-k0a.no",
- "navuotna.no",
- "xn--nvuotna-hwa.no",
- "kafjord.no",
- "xn--kfjord-iua.no",
- "gaivuotna.no",
- "xn--givuotna-8ya.no",
+ "laakesvuemie.no",
+ "xn--lrdal-sra.no",
+ "lahppi.no",
+ "xn--lhppi-xqa.no",
+ "lardal.no",
"larvik.no",
- "lavangen.no",
"lavagis.no",
- "loabat.no",
- "xn--loabt-0qa.no",
+ "lavangen.no",
+ "leangaviika.no",
+ "xn--leagaviika-52b.no",
"lebesby.no",
- "davvesiida.no",
"leikanger.no",
"leirfjord.no",
"leka.no",
"leksvik.no",
"lenvik.no",
- "leangaviika.no",
- "xn--leagaviika-52b.no",
+ "lerdal.no",
"lesja.no",
"levanger.no",
"lier.no",
"lierne.no",
"lillehammer.no",
"lillesand.no",
- "lindesnes.no",
"lindas.no",
"xn--linds-pra.no",
+ "lindesnes.no",
+ "loabat.no",
+ "xn--loabt-0qa.no",
+ "lodingen.no",
+ "xn--ldingen-q1a.no",
"lom.no",
"loppa.no",
- "lahppi.no",
- "xn--lhppi-xqa.no",
+ "lorenskog.no",
+ "xn--lrenskog-54a.no",
+ "loten.no",
+ "xn--lten-gra.no",
"lund.no",
"lunner.no",
"luroy.no",
@@ -4146,25 +4166,19 @@ var rules = [...]string{
"luster.no",
"lyngdal.no",
"lyngen.no",
- "ivgu.no",
- "lardal.no",
- "lerdal.no",
- "xn--lrdal-sra.no",
- "lodingen.no",
- "xn--ldingen-q1a.no",
- "lorenskog.no",
- "xn--lrenskog-54a.no",
- "loten.no",
- "xn--lten-gra.no",
+ "malatvuopmi.no",
+ "xn--mlatvuopmi-s4a.no",
+ "malselv.no",
+ "xn--mlselv-iua.no",
"malvik.no",
- "masoy.no",
- "xn--msy-ula0h.no",
- "muosat.no",
- "xn--muost-0qa.no",
"mandal.no",
"marker.no",
"marnardal.no",
"masfjorden.no",
+ "masoy.no",
+ "xn--msy-ula0h.no",
+ "matta-varjjat.no",
+ "xn--mtta-vrjjat-k7af.no",
"meland.no",
"meldal.no",
"melhus.no",
@@ -4172,39 +4186,39 @@ var rules = [...]string{
"xn--mely-ira.no",
"meraker.no",
"xn--merker-kua.no",
- "moareke.no",
- "xn--moreke-jua.no",
"midsund.no",
"midtre-gauldal.no",
+ "moareke.no",
+ "xn--moreke-jua.no",
"modalen.no",
"modum.no",
"molde.no",
+ "heroy.more-og-romsdal.no",
+ "sande.more-og-romsdal.no",
+ "xn--hery-ira.xn--mre-og-romsdal-qqb.no",
+ "sande.xn--mre-og-romsdal-qqb.no",
"moskenes.no",
"moss.no",
"mosvik.no",
- "malselv.no",
- "xn--mlselv-iua.no",
- "malatvuopmi.no",
- "xn--mlatvuopmi-s4a.no",
+ "muosat.no",
+ "xn--muost-0qa.no",
+ "naamesjevuemie.no",
+ "xn--nmesjevuemie-tcba.no",
+ "xn--nry-yla5g.no",
"namdalseid.no",
- "aejrie.no",
"namsos.no",
"namsskogan.no",
- "naamesjevuemie.no",
- "xn--nmesjevuemie-tcba.no",
- "laakesvuemie.no",
"nannestad.no",
- "narvik.no",
+ "naroy.no",
"narviika.no",
+ "narvik.no",
"naustdal.no",
+ "navuotna.no",
+ "xn--nvuotna-hwa.no",
"nedre-eiker.no",
- "nes.akershus.no",
- "nes.buskerud.no",
"nesna.no",
"nesodden.no",
"nesseby.no",
- "unjarga.no",
- "xn--unjrga-rta.no",
"nesset.no",
"nissedal.no",
"nittedal.no",
@@ -4213,21 +4227,20 @@ var rules = [...]string{
"nord-odal.no",
"norddal.no",
"nordkapp.no",
- "davvenjarga.no",
- "xn--davvenjrga-y4a.no",
+ "bo.nordland.no",
+ "xn--b-5ga.nordland.no",
+ "heroy.nordland.no",
+ "xn--hery-ira.nordland.no",
"nordre-land.no",
"nordreisa.no",
- "raisa.no",
- "xn--risa-5na.no",
"nore-og-uvdal.no",
"notodden.no",
- "naroy.no",
- "xn--nry-yla5g.no",
"notteroy.no",
"xn--nttery-byae.no",
"odda.no",
"oksnes.no",
"xn--ksnes-uua.no",
+ "omasvuotna.no",
"oppdal.no",
"oppegard.no",
"xn--oppegrd-ixa.no",
@@ -4238,11 +4251,11 @@ var rules = [...]string{
"xn--rskog-uua.no",
"orsta.no",
"xn--rsta-fra.no",
- "os.hedmark.no",
- "os.hordaland.no",
"osen.no",
"osteroy.no",
"xn--ostery-fya.no",
+ "valer.ostfold.no",
+ "xn--vler-qoa.xn--stfold-9xa.no",
"ostre-toten.no",
"xn--stre-toten-zcb.no",
"overhalla.no",
@@ -4258,11 +4271,18 @@ var rules = [...]string{
"porsangu.no",
"xn--porsgu-sta26f.no",
"porsgrunn.no",
- "radoy.no",
+ "rade.no",
+ "xn--rde-ula.no",
+ "radoy.no",
"xn--rady-ira.no",
+ "xn--rlingen-mxa.no",
+ "rahkkeravju.no",
+ "xn--rhkkervju-01af.no",
+ "raisa.no",
+ "xn--risa-5na.no",
"rakkestad.no",
+ "ralingen.no",
"rana.no",
- "ruovat.no",
"randaberg.no",
"rauma.no",
"rendalen.no",
@@ -4273,16 +4293,14 @@ var rules = [...]string{
"ringebu.no",
"ringerike.no",
"ringsaker.no",
- "rissa.no",
"risor.no",
"xn--risr-ira.no",
+ "rissa.no",
"roan.no",
- "rollag.no",
- "rygge.no",
- "ralingen.no",
- "xn--rlingen-mxa.no",
"rodoy.no",
"xn--rdy-0nab.no",
+ "rollag.no",
+ "romsa.no",
"romskog.no",
"xn--rmskog-bya.no",
"roros.no",
@@ -4293,18 +4311,14 @@ var rules = [...]string{
"xn--ryken-vua.no",
"royrvik.no",
"xn--ryrvik-bya.no",
- "rade.no",
- "xn--rde-ula.no",
+ "ruovat.no",
+ "rygge.no",
"salangen.no",
- "siellak.no",
- "saltdal.no",
"salat.no",
- "xn--slt-elab.no",
"xn--slat-5na.no",
+ "xn--slt-elab.no",
+ "saltdal.no",
"samnanger.no",
- "sande.more-og-romsdal.no",
- "sande.xn--mre-og-romsdal-qqb.no",
- "sande.vestfold.no",
"sandefjord.no",
"sandnes.no",
"sandoy.no",
@@ -4316,39 +4330,60 @@ var rules = [...]string{
"selbu.no",
"selje.no",
"seljord.no",
+ "siellak.no",
"sigdal.no",
"siljan.no",
"sirdal.no",
+ "skanit.no",
+ "xn--sknit-yqa.no",
+ "skanland.no",
+ "xn--sknland-fxa.no",
"skaun.no",
"skedsmo.no",
"ski.no",
"skien.no",
- "skiptvet.no",
- "skjervoy.no",
- "xn--skjervy-v1a.no",
"skierva.no",
"xn--skierv-uta.no",
+ "skiptvet.no",
"skjak.no",
"xn--skjk-soa.no",
+ "skjervoy.no",
+ "xn--skjervy-v1a.no",
"skodje.no",
- "skanland.no",
- "xn--sknland-fxa.no",
- "skanit.no",
- "xn--sknit-yqa.no",
"smola.no",
"xn--smla-hra.no",
- "snillfjord.no",
+ "snaase.no",
+ "xn--snase-nra.no",
"snasa.no",
"xn--snsa-roa.no",
+ "snillfjord.no",
"snoasa.no",
- "snaase.no",
- "xn--snase-nra.no",
"sogndal.no",
+ "sogne.no",
+ "xn--sgne-gra.no",
"sokndal.no",
"sola.no",
"solund.no",
+ "somna.no",
+ "xn--smna-gra.no",
+ "sondre-land.no",
+ "xn--sndre-land-0cb.no",
"songdalen.no",
+ "sor-aurdal.no",
+ "xn--sr-aurdal-l8a.no",
+ "sor-fron.no",
+ "xn--sr-fron-q1a.no",
+ "sor-odal.no",
+ "xn--sr-odal-q1a.no",
+ "sor-varanger.no",
+ "xn--sr-varanger-ggb.no",
+ "sorfold.no",
+ "xn--srfold-bya.no",
+ "sorreisa.no",
+ "xn--srreisa-q1a.no",
"sortland.no",
+ "sorum.no",
+ "xn--srum-gra.no",
"spydeberg.no",
"stange.no",
"stavanger.no",
@@ -4361,7 +4396,6 @@ var rules = [...]string{
"stord.no",
"stordal.no",
"storfjord.no",
- "omasvuotna.no",
"strand.no",
"stranda.no",
"stryn.no",
@@ -4373,72 +4407,55 @@ var rules = [...]string{
"sveio.no",
"svelvik.no",
"sykkylven.no",
- "sogne.no",
- "xn--sgne-gra.no",
- "somna.no",
- "xn--smna-gra.no",
- "sondre-land.no",
- "xn--sndre-land-0cb.no",
- "sor-aurdal.no",
- "xn--sr-aurdal-l8a.no",
- "sor-fron.no",
- "xn--sr-fron-q1a.no",
- "sor-odal.no",
- "xn--sr-odal-q1a.no",
- "sor-varanger.no",
- "xn--sr-varanger-ggb.no",
- "matta-varjjat.no",
- "xn--mtta-vrjjat-k7af.no",
- "sorfold.no",
- "xn--srfold-bya.no",
- "sorreisa.no",
- "xn--srreisa-q1a.no",
- "sorum.no",
- "xn--srum-gra.no",
"tana.no",
- "deatnu.no",
+ "bo.telemark.no",
+ "xn--b-5ga.telemark.no",
"time.no",
"tingvoll.no",
"tinn.no",
"tjeldsund.no",
- "dielddanuorri.no",
"tjome.no",
"xn--tjme-hra.no",
"tokke.no",
"tolga.no",
+ "tonsberg.no",
+ "xn--tnsberg-q1a.no",
"torsken.no",
+ "xn--trna-woa.no",
+ "trana.no",
"tranoy.no",
"xn--trany-yua.no",
+ "troandin.no",
+ "trogstad.no",
+ "xn--trgstad-r1a.no",
+ "tromsa.no",
"tromso.no",
"xn--troms-zua.no",
- "tromsa.no",
- "romsa.no",
"trondheim.no",
- "troandin.no",
"trysil.no",
- "trana.no",
- "xn--trna-woa.no",
- "trogstad.no",
- "xn--trgstad-r1a.no",
"tvedestrand.no",
"tydal.no",
"tynset.no",
"tysfjord.no",
- "divtasvuodna.no",
- "divttasvuotna.no",
"tysnes.no",
- "tysvar.no",
"xn--tysvr-vra.no",
- "tonsberg.no",
- "xn--tnsberg-q1a.no",
+ "tysvar.no",
"ullensaker.no",
"ullensvang.no",
"ulvik.no",
+ "unjarga.no",
+ "xn--unjrga-rta.no",
"utsira.no",
+ "vaapste.no",
"vadso.no",
"xn--vads-jra.no",
- "cahcesuolo.no",
- "xn--hcesuolo-7ya35b.no",
+ "xn--vry-yla5g.no",
+ "vaga.no",
+ "xn--vg-yiab.no",
+ "vagan.no",
+ "xn--vgan-qoa.no",
+ "vagsoy.no",
+ "xn--vgsy-qoa0j.no",
"vaksdal.no",
"valle.no",
"vang.no",
@@ -4447,8 +4464,8 @@ var rules = [...]string{
"xn--vard-jra.no",
"varggat.no",
"xn--vrggt-xqad.no",
+ "varoy.no",
"vefsn.no",
- "vaapste.no",
"vega.no",
"vegarshei.no",
"xn--vegrshei-c0a.no",
@@ -4456,6 +4473,7 @@ var rules = [...]string{
"verdal.no",
"verran.no",
"vestby.no",
+ "sande.vestfold.no",
"vestnes.no",
"vestre-slidre.no",
"vestre-toten.no",
@@ -4465,30 +4483,18 @@ var rules = [...]string{
"vik.no",
"vikna.no",
"vindafjord.no",
+ "voagat.no",
"volda.no",
"voss.no",
- "varoy.no",
- "xn--vry-yla5g.no",
- "vagan.no",
- "xn--vgan-qoa.no",
- "voagat.no",
- "vagsoy.no",
- "xn--vgsy-qoa0j.no",
- "vaga.no",
- "xn--vg-yiab.no",
- "valer.ostfold.no",
- "xn--vler-qoa.xn--stfold-9xa.no",
- "valer.hedmark.no",
- "xn--vler-qoa.hedmark.no",
"*.np",
"nr",
"biz.nr",
- "info.nr",
- "gov.nr",
+ "com.nr",
"edu.nr",
- "org.nr",
+ "gov.nr",
+ "info.nr",
"net.nr",
- "com.nr",
+ "org.nr",
"nu",
"nz",
"ac.nz",
@@ -4501,8 +4507,8 @@ var rules = [...]string{
"iwi.nz",
"kiwi.nz",
"maori.nz",
- "mil.nz",
"xn--mori-qsa.nz",
+ "mil.nz",
"net.nz",
"org.nz",
"parliament.nz",
@@ -4520,60 +4526,61 @@ var rules = [...]string{
"onion",
"org",
"pa",
+ "abo.pa",
"ac.pa",
- "gob.pa",
"com.pa",
- "org.pa",
- "sld.pa",
"edu.pa",
- "net.pa",
+ "gob.pa",
"ing.pa",
- "abo.pa",
"med.pa",
+ "net.pa",
"nom.pa",
+ "org.pa",
+ "sld.pa",
"pe",
+ "com.pe",
"edu.pe",
"gob.pe",
- "nom.pe",
"mil.pe",
- "org.pe",
- "com.pe",
"net.pe",
+ "nom.pe",
+ "org.pe",
"pf",
"com.pf",
- "org.pf",
"edu.pf",
+ "org.pf",
"*.pg",
"ph",
"com.ph",
- "net.ph",
- "org.ph",
- "gov.ph",
"edu.ph",
- "ngo.ph",
- "mil.ph",
+ "gov.ph",
"i.ph",
+ "mil.ph",
+ "net.ph",
+ "ngo.ph",
+ "org.ph",
"pk",
+ "ac.pk",
+ "biz.pk",
"com.pk",
- "net.pk",
"edu.pk",
- "org.pk",
"fam.pk",
- "biz.pk",
- "web.pk",
- "gov.pk",
+ "gkp.pk",
"gob.pk",
+ "gog.pk",
"gok.pk",
- "gon.pk",
"gop.pk",
"gos.pk",
- "info.pk",
+ "gov.pk",
+ "net.pk",
+ "org.pk",
+ "web.pk",
"pl",
"com.pl",
"net.pl",
"org.pl",
- "aid.pl",
"agro.pl",
+ "aid.pl",
"atm.pl",
"auto.pl",
"biz.pl",
@@ -4582,8 +4589,8 @@ var rules = [...]string{
"gsm.pl",
"info.pl",
"mail.pl",
- "miasta.pl",
"media.pl",
+ "miasta.pl",
"mil.pl",
"nieruchomosci.pl",
"nom.pl",
@@ -4684,11 +4691,11 @@ var rules = [...]string{
"jelenia-gora.pl",
"jgora.pl",
"kalisz.pl",
- "kazimierz-dolny.pl",
"karpacz.pl",
"kartuzy.pl",
"kaszuby.pl",
"katowice.pl",
+ "kazimierz-dolny.pl",
"kepno.pl",
"ketrzyn.pl",
"klodzko.pl",
@@ -4731,8 +4738,8 @@ var rules = [...]string{
"podhale.pl",
"podlasie.pl",
"polkowice.pl",
- "pomorze.pl",
"pomorskie.pl",
+ "pomorze.pl",
"prochowice.pl",
"pruszkow.pl",
"przeworsk.pl",
@@ -4743,11 +4750,11 @@ var rules = [...]string{
"rzeszow.pl",
"sanok.pl",
"sejny.pl",
+ "skoczow.pl",
"slask.pl",
"slupsk.pl",
"sosnowiec.pl",
"stalowa-wola.pl",
- "skoczow.pl",
"starachowice.pl",
"stargard.pl",
"suwalki.pl",
@@ -4779,26 +4786,26 @@ var rules = [...]string{
"zgorzelec.pl",
"pm",
"pn",
- "gov.pn",
"co.pn",
- "org.pn",
"edu.pn",
+ "gov.pn",
"net.pn",
+ "org.pn",
"post",
"pr",
+ "biz.pr",
"com.pr",
- "net.pr",
- "org.pr",
- "gov.pr",
"edu.pr",
- "isla.pr",
- "pro.pr",
- "biz.pr",
+ "gov.pr",
"info.pr",
+ "isla.pr",
"name.pr",
+ "net.pr",
+ "org.pr",
+ "pro.pr",
+ "ac.pr",
"est.pr",
"prof.pr",
- "ac.pr",
"pro",
"aaa.pro",
"aca.pro",
@@ -4812,29 +4819,24 @@ var rules = [...]string{
"med.pro",
"recht.pro",
"ps",
+ "com.ps",
"edu.ps",
"gov.ps",
- "sec.ps",
- "plo.ps",
- "com.ps",
- "org.ps",
"net.ps",
+ "org.ps",
+ "plo.ps",
+ "sec.ps",
"pt",
- "net.pt",
- "gov.pt",
- "org.pt",
+ "com.pt",
"edu.pt",
+ "gov.pt",
"int.pt",
- "publ.pt",
- "com.pt",
+ "net.pt",
"nome.pt",
+ "org.pt",
+ "publ.pt",
"pw",
- "co.pw",
- "ne.pw",
- "or.pw",
- "ed.pw",
- "go.pw",
- "belau.pw",
+ "gov.pw",
"py",
"com.py",
"coop.py",
@@ -4855,7 +4857,6 @@ var rules = [...]string{
"re",
"asso.re",
"com.re",
- "nom.re",
"ro",
"arts.ro",
"com.ro",
@@ -4886,12 +4887,12 @@ var rules = [...]string{
"org.rw",
"sa",
"com.sa",
- "net.sa",
- "org.sa",
+ "edu.sa",
"gov.sa",
"med.sa",
+ "net.sa",
+ "org.sa",
"pub.sa",
- "edu.sa",
"sch.sa",
"sb",
"com.sb",
@@ -4901,21 +4902,21 @@ var rules = [...]string{
"org.sb",
"sc",
"com.sc",
+ "edu.sc",
"gov.sc",
"net.sc",
"org.sc",
- "edu.sc",
"sd",
"com.sd",
- "net.sd",
- "org.sd",
"edu.sd",
- "med.sd",
- "tv.sd",
"gov.sd",
"info.sd",
- "se",
- "a.se",
+ "med.sd",
+ "net.sd",
+ "org.sd",
+ "tv.sd",
+ "se",
+ "a.se",
"ac.se",
"b.se",
"bd.se",
@@ -4956,25 +4957,24 @@ var rules = [...]string{
"z.se",
"sg",
"com.sg",
+ "edu.sg",
+ "gov.sg",
"net.sg",
"org.sg",
- "gov.sg",
- "edu.sg",
- "per.sg",
"sh",
"com.sh",
- "net.sh",
"gov.sh",
- "org.sh",
"mil.sh",
+ "net.sh",
+ "org.sh",
"si",
"sj",
"sk",
"sl",
"com.sl",
- "net.sl",
"edu.sl",
"gov.sl",
+ "net.sl",
"org.sl",
"sm",
"sn",
@@ -4995,6 +4995,7 @@ var rules = [...]string{
"sr",
"ss",
"biz.ss",
+ "co.ss",
"com.ss",
"edu.ss",
"gov.ss",
@@ -5024,15 +5025,15 @@ var rules = [...]string{
"sx",
"gov.sx",
"sy",
+ "com.sy",
"edu.sy",
"gov.sy",
- "net.sy",
"mil.sy",
- "com.sy",
+ "net.sy",
"org.sy",
"sz",
- "co.sz",
"ac.sz",
+ "co.sz",
"org.sz",
"tc",
"td",
@@ -5067,14 +5068,14 @@ var rules = [...]string{
"tl",
"gov.tl",
"tm",
- "com.tm",
"co.tm",
- "org.tm",
- "net.tm",
- "nom.tm",
+ "com.tm",
+ "edu.tm",
"gov.tm",
"mil.tm",
- "edu.tm",
+ "net.tm",
+ "nom.tm",
+ "org.tm",
"tn",
"com.tn",
"ens.tn",
@@ -5091,11 +5092,11 @@ var rules = [...]string{
"tourism.tn",
"to",
"com.to",
+ "edu.to",
"gov.to",
+ "mil.to",
"net.to",
"org.to",
- "edu.to",
- "mil.to",
"tr",
"av.tr",
"bbs.tr",
@@ -5107,9 +5108,9 @@ var rules = [...]string{
"gen.tr",
"gov.tr",
"info.tr",
- "mil.tr",
"k12.tr",
"kep.tr",
+ "mil.tr",
"name.tr",
"net.tr",
"org.tr",
@@ -5121,38 +5122,29 @@ var rules = [...]string{
"nc.tr",
"gov.nc.tr",
"tt",
+ "biz.tt",
"co.tt",
"com.tt",
- "org.tt",
- "net.tt",
- "biz.tt",
+ "edu.tt",
+ "gov.tt",
"info.tt",
- "pro.tt",
- "int.tt",
- "coop.tt",
- "jobs.tt",
- "mobi.tt",
- "travel.tt",
- "museum.tt",
- "aero.tt",
+ "mil.tt",
"name.tt",
- "gov.tt",
- "edu.tt",
+ "net.tt",
+ "org.tt",
+ "pro.tt",
"tv",
"tw",
+ "club.tw",
+ "com.tw",
+ "ebiz.tw",
"edu.tw",
+ "game.tw",
"gov.tw",
+ "idv.tw",
"mil.tw",
- "com.tw",
"net.tw",
"org.tw",
- "idv.tw",
- "game.tw",
- "ebiz.tw",
- "club.tw",
- "xn--zf0ao64a.tw",
- "xn--uc0atv.tw",
- "xn--czrw28b.tw",
"tz",
"ac.tz",
"co.tz",
@@ -5209,6 +5201,7 @@ var rules = [...]string{
"lg.ua",
"lt.ua",
"lugansk.ua",
+ "luhansk.ua",
"lutsk.ua",
"lv.ua",
"lviv.ua",
@@ -5232,11 +5225,13 @@ var rules = [...]string{
"ternopil.ua",
"uz.ua",
"uzhgorod.ua",
+ "uzhhorod.ua",
"vinnica.ua",
"vinnytsia.ua",
"vn.ua",
"volyn.ua",
"yalta.ua",
+ "zakarpattia.ua",
"zaporizhzhe.ua",
"zaporizhzhia.ua",
"zhitomir.ua",
@@ -5244,14 +5239,18 @@ var rules = [...]string{
"zp.ua",
"zt.ua",
"ug",
- "co.ug",
- "or.ug",
"ac.ug",
- "sc.ug",
+ "co.ug",
+ "com.ug",
+ "edu.ug",
"go.ug",
+ "gov.ug",
+ "mil.ug",
"ne.ug",
- "com.ug",
+ "or.ug",
"org.ug",
+ "sc.ug",
+ "us.ug",
"uk",
"ac.uk",
"co.uk",
@@ -5266,9 +5265,7 @@ var rules = [...]string{
"*.sch.uk",
"us",
"dni.us",
- "fed.us",
"isa.us",
- "kids.us",
"nsn.us",
"ak.us",
"al.us",
@@ -5318,9 +5315,9 @@ var rules = [...]string{
"tn.us",
"tx.us",
"ut.us",
+ "va.us",
"vi.us",
"vt.us",
- "va.us",
"wa.us",
"wi.us",
"wv.us",
@@ -5334,7 +5331,6 @@ var rules = [...]string{
"k12.co.us",
"k12.ct.us",
"k12.dc.us",
- "k12.de.us",
"k12.fl.us",
"k12.ga.us",
"k12.gu.us",
@@ -5369,21 +5365,29 @@ var rules = [...]string{
"k12.tn.us",
"k12.tx.us",
"k12.ut.us",
+ "k12.va.us",
"k12.vi.us",
"k12.vt.us",
- "k12.va.us",
"k12.wa.us",
"k12.wi.us",
- "k12.wy.us",
"cc.ak.us",
+ "lib.ak.us",
"cc.al.us",
+ "lib.al.us",
"cc.ar.us",
+ "lib.ar.us",
"cc.as.us",
+ "lib.as.us",
"cc.az.us",
+ "lib.az.us",
"cc.ca.us",
+ "lib.ca.us",
"cc.co.us",
+ "lib.co.us",
"cc.ct.us",
+ "lib.ct.us",
"cc.dc.us",
+ "lib.dc.us",
"cc.de.us",
"cc.fl.us",
"cc.ga.us",
@@ -5423,22 +5427,14 @@ var rules = [...]string{
"cc.tn.us",
"cc.tx.us",
"cc.ut.us",
+ "cc.va.us",
"cc.vi.us",
"cc.vt.us",
- "cc.va.us",
"cc.wa.us",
"cc.wi.us",
"cc.wv.us",
"cc.wy.us",
- "lib.ak.us",
- "lib.al.us",
- "lib.ar.us",
- "lib.as.us",
- "lib.az.us",
- "lib.ca.us",
- "lib.co.us",
- "lib.ct.us",
- "lib.dc.us",
+ "k12.wy.us",
"lib.fl.us",
"lib.ga.us",
"lib.gu.us",
@@ -5477,15 +5473,15 @@ var rules = [...]string{
"lib.tn.us",
"lib.tx.us",
"lib.ut.us",
+ "lib.va.us",
"lib.vi.us",
"lib.vt.us",
- "lib.va.us",
"lib.wa.us",
"lib.wi.us",
"lib.wy.us",
- "pvt.k12.ma.us",
"chtr.k12.ma.us",
"paroch.k12.ma.us",
+ "pvt.k12.ma.us",
"ann-arbor.mi.us",
"cog.mi.us",
"dst.mi.us",
@@ -5509,11 +5505,11 @@ var rules = [...]string{
"va",
"vc",
"com.vc",
- "net.vc",
- "org.vc",
+ "edu.vc",
"gov.vc",
"mil.vc",
- "edu.vc",
+ "net.vc",
+ "org.vc",
"ve",
"arts.ve",
"bib.ve",
@@ -5521,6 +5517,7 @@ var rules = [...]string{
"com.ve",
"e12.ve",
"edu.ve",
+ "emprende.ve",
"firm.ve",
"gob.ve",
"gov.ve",
@@ -5536,6 +5533,7 @@ var rules = [...]string{
"tec.ve",
"web.ve",
"vg",
+ "edu.vg",
"vi",
"co.vi",
"com.vi",
@@ -5629,10 +5627,10 @@ var rules = [...]string{
"wf",
"ws",
"com.ws",
+ "edu.ws",
+ "gov.ws",
"net.ws",
"org.ws",
- "gov.ws",
- "edu.ws",
"yt",
"xn--mgbaam7a8h",
"xn--y9a3aq",
@@ -5650,12 +5648,12 @@ var rules = [...]string{
"xn--node",
"xn--qxam",
"xn--j6w193g",
+ "xn--gmqw5a.xn--j6w193g",
"xn--55qx5d.xn--j6w193g",
- "xn--wcvs22d.xn--j6w193g",
"xn--mxtq1m.xn--j6w193g",
- "xn--gmqw5a.xn--j6w193g",
- "xn--od0alg.xn--j6w193g",
+ "xn--wcvs22d.xn--j6w193g",
"xn--uc0atv.xn--j6w193g",
+ "xn--od0alg.xn--j6w193g",
"xn--2scrj9c",
"xn--3hcrj9c",
"xn--45br5cyl",
@@ -5691,12 +5689,12 @@ var rules = [...]string{
"xn--mgbai9a5eva00b",
"xn--ygbi2ammx",
"xn--90a3ac",
- "xn--o1ac.xn--90a3ac",
- "xn--c1avg.xn--90a3ac",
+ "xn--80au.xn--90a3ac",
"xn--90azh.xn--90a3ac",
"xn--d1at.xn--90a3ac",
+ "xn--c1avg.xn--90a3ac",
+ "xn--o1ac.xn--90a3ac",
"xn--o1ach.xn--90a3ac",
- "xn--80au.xn--90a3ac",
"xn--p1ai",
"xn--wgbl6a",
"xn--mgberp4a5d4ar",
@@ -5709,11 +5707,11 @@ var rules = [...]string{
"xn--ogbpf8fl",
"xn--mgbtf8fl",
"xn--o3cw4h",
- "xn--12c1fe0br.xn--o3cw4h",
- "xn--12co0c3b4eva.xn--o3cw4h",
- "xn--h3cuzk1di.xn--o3cw4h",
"xn--o3cyx2a.xn--o3cw4h",
+ "xn--12co0c3b4eva.xn--o3cw4h",
"xn--m3ch0j3a.xn--o3cw4h",
+ "xn--h3cuzk1di.xn--o3cw4h",
+ "xn--12c1fe0br.xn--o3cw4h",
"xn--12cfi8ixb8l.xn--o3cw4h",
"xn--pgbs0dh",
"xn--kpry57d",
@@ -5726,8 +5724,8 @@ var rules = [...]string{
"com.ye",
"edu.ye",
"gov.ye",
- "net.ye",
"mil.ye",
+ "net.ye",
"org.ye",
"ac.za",
"agric.za",
@@ -5834,14 +5832,12 @@ var rules = [...]string{
"author",
"auto",
"autos",
- "avianca",
"aws",
"axa",
"azure",
"baby",
"baidu",
"banamex",
- "bananarepublic",
"band",
"bank",
"bar",
@@ -5937,7 +5933,6 @@ var rules = [...]string{
"cba",
"cbn",
"cbre",
- "cbs",
"center",
"ceo",
"cern",
@@ -5960,7 +5955,6 @@ var rules = [...]string{
"citi",
"citic",
"city",
- "cityeats",
"claims",
"cleaning",
"click",
@@ -5975,7 +5969,6 @@ var rules = [...]string{
"coffee",
"college",
"cologne",
- "comcast",
"commbank",
"community",
"company",
@@ -6006,7 +5999,6 @@ var rules = [...]string{
"cuisinella",
"cymru",
"cyou",
- "dabur",
"dad",
"dance",
"data",
@@ -6072,7 +6064,6 @@ var rules = [...]string{
"erni",
"esq",
"estate",
- "etisalat",
"eurovision",
"eus",
"events",
@@ -6128,7 +6119,6 @@ var rules = [...]string{
"fresenius",
"frl",
"frogans",
- "frontdoor",
"frontier",
"ftr",
"fujitsu",
@@ -6182,7 +6172,6 @@ var rules = [...]string{
"gripe",
"grocery",
"group",
- "guardian",
"gucci",
"guge",
"guide",
@@ -6276,13 +6265,11 @@ var rules = [...]string{
"kaufen",
"kddi",
"kerryhotels",
- "kerrylogistics",
"kerryproperties",
"kfh",
"kia",
"kids",
"kim",
- "kinder",
"kindle",
"kitchen",
"kiwi",
@@ -6327,7 +6314,6 @@ var rules = [...]string{
"limo",
"lincoln",
"link",
- "lipsy",
"live",
"living",
"llc",
@@ -6372,6 +6358,7 @@ var rules = [...]string{
"memorial",
"men",
"menu",
+ "merck",
"merckmsd",
"miami",
"microsoft",
@@ -6403,7 +6390,6 @@ var rules = [...]string{
"music",
"nab",
"nagoya",
- "natura",
"navy",
"nba",
"nec",
@@ -6426,7 +6412,6 @@ var rules = [...]string{
"nissan",
"nissay",
"nokia",
- "northwesternmutual",
"norton",
"now",
"nowruz",
@@ -6441,7 +6426,6 @@ var rules = [...]string{
"okinawa",
"olayan",
"olayangroup",
- "oldnavy",
"ollo",
"omega",
"one",
@@ -6547,7 +6531,6 @@ var rules = [...]string{
"ril",
"rio",
"rip",
- "rocher",
"rocks",
"rodeo",
"rogers",
@@ -6576,7 +6559,6 @@ var rules = [...]string{
"saxo",
"sbi",
"sbs",
- "sca",
"scb",
"schaeffler",
"schmidt",
@@ -6601,7 +6583,6 @@ var rules = [...]string{
"sfr",
"shangrila",
"sharp",
- "shaw",
"shell",
"shia",
"shiksha",
@@ -6610,7 +6591,6 @@ var rules = [...]string{
"shopping",
"shouji",
"show",
- "showtime",
"silk",
"sina",
"singles",
@@ -6747,7 +6727,6 @@ var rules = [...]string{
"vivo",
"vlaanderen",
"vodka",
- "volkswagen",
"volvo",
"vote",
"voting",
@@ -6765,6 +6744,7 @@ var rules = [...]string{
"webcam",
"weber",
"website",
+ "wed",
"wedding",
"weibo",
"weir",
@@ -6787,7 +6767,6 @@ var rules = [...]string{
"wtf",
"xbox",
"xerox",
- "xfinity",
"xihuan",
"xin",
"xn--11b4c3d",
@@ -6848,7 +6827,6 @@ var rules = [...]string{
"xn--kput3i",
"xn--mgba3a3ejt",
"xn--mgba7c0bbn0a",
- "xn--mgbaakc7dvf",
"xn--mgbab2bd",
"xn--mgbca7dzdo",
"xn--mgbi4ecexp",
@@ -6898,26 +6876,47 @@ var rules = [...]string{
"zip",
"zone",
"zuerich",
+ "co.krd",
+ "edu.krd",
+ "art.pl",
+ "gliwice.pl",
+ "krakow.pl",
+ "poznan.pl",
+ "wroc.pl",
+ "zakopane.pl",
+ "lib.de.us",
+ "12chars.dev",
+ "12chars.it",
+ "12chars.pro",
"cc.ua",
"inf.ua",
"ltd.ua",
"611.to",
- "graphox.us",
- "*.devcdnaccesso.com",
+ "a2hosted.com",
+ "cpserver.com",
"*.on-acorn.io",
"activetrail.biz",
+ "adaptable.app",
+ "myaddr.dev",
+ "myaddr.io",
+ "dyn.addr.tools",
+ "myaddr.tools",
"adobeaemcloud.com",
"*.dev.adobeaemcloud.com",
+ "aem.live",
"hlx.live",
"adobeaemcloud.net",
+ "aem.page",
"hlx.page",
"hlx3.page",
"adobeio-static.net",
"adobeioruntime.net",
+ "africa.com",
"beep.pl",
"airkitapps.com",
"airkitapps-au.com",
"airkitapps.eu",
+ "aiven.app",
"aivencloud.com",
"akadns.net",
"akamai.net",
@@ -6937,53 +6936,432 @@ var rules = [...]string{
"barsy.ca",
"*.compute.estate",
"*.alces.network",
+ "alibabacloudcs.com",
"kasserver.com",
"altervista.org",
"alwaysdata.net",
"myamaze.net",
+ "execute-api.cn-north-1.amazonaws.com.cn",
+ "execute-api.cn-northwest-1.amazonaws.com.cn",
+ "execute-api.af-south-1.amazonaws.com",
+ "execute-api.ap-east-1.amazonaws.com",
+ "execute-api.ap-northeast-1.amazonaws.com",
+ "execute-api.ap-northeast-2.amazonaws.com",
+ "execute-api.ap-northeast-3.amazonaws.com",
+ "execute-api.ap-south-1.amazonaws.com",
+ "execute-api.ap-south-2.amazonaws.com",
+ "execute-api.ap-southeast-1.amazonaws.com",
+ "execute-api.ap-southeast-2.amazonaws.com",
+ "execute-api.ap-southeast-3.amazonaws.com",
+ "execute-api.ap-southeast-4.amazonaws.com",
+ "execute-api.ap-southeast-5.amazonaws.com",
+ "execute-api.ca-central-1.amazonaws.com",
+ "execute-api.ca-west-1.amazonaws.com",
+ "execute-api.eu-central-1.amazonaws.com",
+ "execute-api.eu-central-2.amazonaws.com",
+ "execute-api.eu-north-1.amazonaws.com",
+ "execute-api.eu-south-1.amazonaws.com",
+ "execute-api.eu-south-2.amazonaws.com",
+ "execute-api.eu-west-1.amazonaws.com",
+ "execute-api.eu-west-2.amazonaws.com",
+ "execute-api.eu-west-3.amazonaws.com",
+ "execute-api.il-central-1.amazonaws.com",
+ "execute-api.me-central-1.amazonaws.com",
+ "execute-api.me-south-1.amazonaws.com",
+ "execute-api.sa-east-1.amazonaws.com",
+ "execute-api.us-east-1.amazonaws.com",
+ "execute-api.us-east-2.amazonaws.com",
+ "execute-api.us-gov-east-1.amazonaws.com",
+ "execute-api.us-gov-west-1.amazonaws.com",
+ "execute-api.us-west-1.amazonaws.com",
+ "execute-api.us-west-2.amazonaws.com",
"cloudfront.net",
+ "auth.af-south-1.amazoncognito.com",
+ "auth.ap-east-1.amazoncognito.com",
+ "auth.ap-northeast-1.amazoncognito.com",
+ "auth.ap-northeast-2.amazoncognito.com",
+ "auth.ap-northeast-3.amazoncognito.com",
+ "auth.ap-south-1.amazoncognito.com",
+ "auth.ap-south-2.amazoncognito.com",
+ "auth.ap-southeast-1.amazoncognito.com",
+ "auth.ap-southeast-2.amazoncognito.com",
+ "auth.ap-southeast-3.amazoncognito.com",
+ "auth.ap-southeast-4.amazoncognito.com",
+ "auth.ca-central-1.amazoncognito.com",
+ "auth.ca-west-1.amazoncognito.com",
+ "auth.eu-central-1.amazoncognito.com",
+ "auth.eu-central-2.amazoncognito.com",
+ "auth.eu-north-1.amazoncognito.com",
+ "auth.eu-south-1.amazoncognito.com",
+ "auth.eu-south-2.amazoncognito.com",
+ "auth.eu-west-1.amazoncognito.com",
+ "auth.eu-west-2.amazoncognito.com",
+ "auth.eu-west-3.amazoncognito.com",
+ "auth.il-central-1.amazoncognito.com",
+ "auth.me-central-1.amazoncognito.com",
+ "auth.me-south-1.amazoncognito.com",
+ "auth.sa-east-1.amazoncognito.com",
+ "auth.us-east-1.amazoncognito.com",
+ "auth-fips.us-east-1.amazoncognito.com",
+ "auth.us-east-2.amazoncognito.com",
+ "auth-fips.us-east-2.amazoncognito.com",
+ "auth-fips.us-gov-west-1.amazoncognito.com",
+ "auth.us-west-1.amazoncognito.com",
+ "auth-fips.us-west-1.amazoncognito.com",
+ "auth.us-west-2.amazoncognito.com",
+ "auth-fips.us-west-2.amazoncognito.com",
+ "*.compute.amazonaws.com.cn",
"*.compute.amazonaws.com",
"*.compute-1.amazonaws.com",
- "*.compute.amazonaws.com.cn",
"us-east-1.amazonaws.com",
+ "emrappui-prod.cn-north-1.amazonaws.com.cn",
+ "emrnotebooks-prod.cn-north-1.amazonaws.com.cn",
+ "emrstudio-prod.cn-north-1.amazonaws.com.cn",
+ "emrappui-prod.cn-northwest-1.amazonaws.com.cn",
+ "emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn",
+ "emrstudio-prod.cn-northwest-1.amazonaws.com.cn",
+ "emrappui-prod.af-south-1.amazonaws.com",
+ "emrnotebooks-prod.af-south-1.amazonaws.com",
+ "emrstudio-prod.af-south-1.amazonaws.com",
+ "emrappui-prod.ap-east-1.amazonaws.com",
+ "emrnotebooks-prod.ap-east-1.amazonaws.com",
+ "emrstudio-prod.ap-east-1.amazonaws.com",
+ "emrappui-prod.ap-northeast-1.amazonaws.com",
+ "emrnotebooks-prod.ap-northeast-1.amazonaws.com",
+ "emrstudio-prod.ap-northeast-1.amazonaws.com",
+ "emrappui-prod.ap-northeast-2.amazonaws.com",
+ "emrnotebooks-prod.ap-northeast-2.amazonaws.com",
+ "emrstudio-prod.ap-northeast-2.amazonaws.com",
+ "emrappui-prod.ap-northeast-3.amazonaws.com",
+ "emrnotebooks-prod.ap-northeast-3.amazonaws.com",
+ "emrstudio-prod.ap-northeast-3.amazonaws.com",
+ "emrappui-prod.ap-south-1.amazonaws.com",
+ "emrnotebooks-prod.ap-south-1.amazonaws.com",
+ "emrstudio-prod.ap-south-1.amazonaws.com",
+ "emrappui-prod.ap-south-2.amazonaws.com",
+ "emrnotebooks-prod.ap-south-2.amazonaws.com",
+ "emrstudio-prod.ap-south-2.amazonaws.com",
+ "emrappui-prod.ap-southeast-1.amazonaws.com",
+ "emrnotebooks-prod.ap-southeast-1.amazonaws.com",
+ "emrstudio-prod.ap-southeast-1.amazonaws.com",
+ "emrappui-prod.ap-southeast-2.amazonaws.com",
+ "emrnotebooks-prod.ap-southeast-2.amazonaws.com",
+ "emrstudio-prod.ap-southeast-2.amazonaws.com",
+ "emrappui-prod.ap-southeast-3.amazonaws.com",
+ "emrnotebooks-prod.ap-southeast-3.amazonaws.com",
+ "emrstudio-prod.ap-southeast-3.amazonaws.com",
+ "emrappui-prod.ap-southeast-4.amazonaws.com",
+ "emrnotebooks-prod.ap-southeast-4.amazonaws.com",
+ "emrstudio-prod.ap-southeast-4.amazonaws.com",
+ "emrappui-prod.ca-central-1.amazonaws.com",
+ "emrnotebooks-prod.ca-central-1.amazonaws.com",
+ "emrstudio-prod.ca-central-1.amazonaws.com",
+ "emrappui-prod.ca-west-1.amazonaws.com",
+ "emrnotebooks-prod.ca-west-1.amazonaws.com",
+ "emrstudio-prod.ca-west-1.amazonaws.com",
+ "emrappui-prod.eu-central-1.amazonaws.com",
+ "emrnotebooks-prod.eu-central-1.amazonaws.com",
+ "emrstudio-prod.eu-central-1.amazonaws.com",
+ "emrappui-prod.eu-central-2.amazonaws.com",
+ "emrnotebooks-prod.eu-central-2.amazonaws.com",
+ "emrstudio-prod.eu-central-2.amazonaws.com",
+ "emrappui-prod.eu-north-1.amazonaws.com",
+ "emrnotebooks-prod.eu-north-1.amazonaws.com",
+ "emrstudio-prod.eu-north-1.amazonaws.com",
+ "emrappui-prod.eu-south-1.amazonaws.com",
+ "emrnotebooks-prod.eu-south-1.amazonaws.com",
+ "emrstudio-prod.eu-south-1.amazonaws.com",
+ "emrappui-prod.eu-south-2.amazonaws.com",
+ "emrnotebooks-prod.eu-south-2.amazonaws.com",
+ "emrstudio-prod.eu-south-2.amazonaws.com",
+ "emrappui-prod.eu-west-1.amazonaws.com",
+ "emrnotebooks-prod.eu-west-1.amazonaws.com",
+ "emrstudio-prod.eu-west-1.amazonaws.com",
+ "emrappui-prod.eu-west-2.amazonaws.com",
+ "emrnotebooks-prod.eu-west-2.amazonaws.com",
+ "emrstudio-prod.eu-west-2.amazonaws.com",
+ "emrappui-prod.eu-west-3.amazonaws.com",
+ "emrnotebooks-prod.eu-west-3.amazonaws.com",
+ "emrstudio-prod.eu-west-3.amazonaws.com",
+ "emrappui-prod.il-central-1.amazonaws.com",
+ "emrnotebooks-prod.il-central-1.amazonaws.com",
+ "emrstudio-prod.il-central-1.amazonaws.com",
+ "emrappui-prod.me-central-1.amazonaws.com",
+ "emrnotebooks-prod.me-central-1.amazonaws.com",
+ "emrstudio-prod.me-central-1.amazonaws.com",
+ "emrappui-prod.me-south-1.amazonaws.com",
+ "emrnotebooks-prod.me-south-1.amazonaws.com",
+ "emrstudio-prod.me-south-1.amazonaws.com",
+ "emrappui-prod.sa-east-1.amazonaws.com",
+ "emrnotebooks-prod.sa-east-1.amazonaws.com",
+ "emrstudio-prod.sa-east-1.amazonaws.com",
+ "emrappui-prod.us-east-1.amazonaws.com",
+ "emrnotebooks-prod.us-east-1.amazonaws.com",
+ "emrstudio-prod.us-east-1.amazonaws.com",
+ "emrappui-prod.us-east-2.amazonaws.com",
+ "emrnotebooks-prod.us-east-2.amazonaws.com",
+ "emrstudio-prod.us-east-2.amazonaws.com",
+ "emrappui-prod.us-gov-east-1.amazonaws.com",
+ "emrnotebooks-prod.us-gov-east-1.amazonaws.com",
+ "emrstudio-prod.us-gov-east-1.amazonaws.com",
+ "emrappui-prod.us-gov-west-1.amazonaws.com",
+ "emrnotebooks-prod.us-gov-west-1.amazonaws.com",
+ "emrstudio-prod.us-gov-west-1.amazonaws.com",
+ "emrappui-prod.us-west-1.amazonaws.com",
+ "emrnotebooks-prod.us-west-1.amazonaws.com",
+ "emrstudio-prod.us-west-1.amazonaws.com",
+ "emrappui-prod.us-west-2.amazonaws.com",
+ "emrnotebooks-prod.us-west-2.amazonaws.com",
+ "emrstudio-prod.us-west-2.amazonaws.com",
+ "*.cn-north-1.airflow.amazonaws.com.cn",
+ "*.cn-northwest-1.airflow.amazonaws.com.cn",
+ "*.af-south-1.airflow.amazonaws.com",
+ "*.ap-east-1.airflow.amazonaws.com",
+ "*.ap-northeast-1.airflow.amazonaws.com",
+ "*.ap-northeast-2.airflow.amazonaws.com",
+ "*.ap-northeast-3.airflow.amazonaws.com",
+ "*.ap-south-1.airflow.amazonaws.com",
+ "*.ap-south-2.airflow.amazonaws.com",
+ "*.ap-southeast-1.airflow.amazonaws.com",
+ "*.ap-southeast-2.airflow.amazonaws.com",
+ "*.ap-southeast-3.airflow.amazonaws.com",
+ "*.ap-southeast-4.airflow.amazonaws.com",
+ "*.ca-central-1.airflow.amazonaws.com",
+ "*.ca-west-1.airflow.amazonaws.com",
+ "*.eu-central-1.airflow.amazonaws.com",
+ "*.eu-central-2.airflow.amazonaws.com",
+ "*.eu-north-1.airflow.amazonaws.com",
+ "*.eu-south-1.airflow.amazonaws.com",
+ "*.eu-south-2.airflow.amazonaws.com",
+ "*.eu-west-1.airflow.amazonaws.com",
+ "*.eu-west-2.airflow.amazonaws.com",
+ "*.eu-west-3.airflow.amazonaws.com",
+ "*.il-central-1.airflow.amazonaws.com",
+ "*.me-central-1.airflow.amazonaws.com",
+ "*.me-south-1.airflow.amazonaws.com",
+ "*.sa-east-1.airflow.amazonaws.com",
+ "*.us-east-1.airflow.amazonaws.com",
+ "*.us-east-2.airflow.amazonaws.com",
+ "*.us-west-1.airflow.amazonaws.com",
+ "*.us-west-2.airflow.amazonaws.com",
+ "s3.dualstack.cn-north-1.amazonaws.com.cn",
+ "s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn",
+ "s3-website.dualstack.cn-north-1.amazonaws.com.cn",
"s3.cn-north-1.amazonaws.com.cn",
+ "s3-accesspoint.cn-north-1.amazonaws.com.cn",
+ "s3-deprecated.cn-north-1.amazonaws.com.cn",
+ "s3-object-lambda.cn-north-1.amazonaws.com.cn",
+ "s3-website.cn-north-1.amazonaws.com.cn",
+ "s3.dualstack.cn-northwest-1.amazonaws.com.cn",
+ "s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn",
+ "s3.cn-northwest-1.amazonaws.com.cn",
+ "s3-accesspoint.cn-northwest-1.amazonaws.com.cn",
+ "s3-object-lambda.cn-northwest-1.amazonaws.com.cn",
+ "s3-website.cn-northwest-1.amazonaws.com.cn",
+ "s3.dualstack.af-south-1.amazonaws.com",
+ "s3-accesspoint.dualstack.af-south-1.amazonaws.com",
+ "s3-website.dualstack.af-south-1.amazonaws.com",
+ "s3.af-south-1.amazonaws.com",
+ "s3-accesspoint.af-south-1.amazonaws.com",
+ "s3-object-lambda.af-south-1.amazonaws.com",
+ "s3-website.af-south-1.amazonaws.com",
+ "s3.dualstack.ap-east-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-east-1.amazonaws.com",
+ "s3.ap-east-1.amazonaws.com",
+ "s3-accesspoint.ap-east-1.amazonaws.com",
+ "s3-object-lambda.ap-east-1.amazonaws.com",
+ "s3-website.ap-east-1.amazonaws.com",
"s3.dualstack.ap-northeast-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com",
+ "s3-website.dualstack.ap-northeast-1.amazonaws.com",
+ "s3.ap-northeast-1.amazonaws.com",
+ "s3-accesspoint.ap-northeast-1.amazonaws.com",
+ "s3-object-lambda.ap-northeast-1.amazonaws.com",
+ "s3-website.ap-northeast-1.amazonaws.com",
"s3.dualstack.ap-northeast-2.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com",
+ "s3-website.dualstack.ap-northeast-2.amazonaws.com",
"s3.ap-northeast-2.amazonaws.com",
+ "s3-accesspoint.ap-northeast-2.amazonaws.com",
+ "s3-object-lambda.ap-northeast-2.amazonaws.com",
"s3-website.ap-northeast-2.amazonaws.com",
+ "s3.dualstack.ap-northeast-3.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com",
+ "s3-website.dualstack.ap-northeast-3.amazonaws.com",
+ "s3.ap-northeast-3.amazonaws.com",
+ "s3-accesspoint.ap-northeast-3.amazonaws.com",
+ "s3-object-lambda.ap-northeast-3.amazonaws.com",
+ "s3-website.ap-northeast-3.amazonaws.com",
"s3.dualstack.ap-south-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-south-1.amazonaws.com",
+ "s3-website.dualstack.ap-south-1.amazonaws.com",
"s3.ap-south-1.amazonaws.com",
+ "s3-accesspoint.ap-south-1.amazonaws.com",
+ "s3-object-lambda.ap-south-1.amazonaws.com",
"s3-website.ap-south-1.amazonaws.com",
+ "s3.dualstack.ap-south-2.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-south-2.amazonaws.com",
+ "s3-website.dualstack.ap-south-2.amazonaws.com",
+ "s3.ap-south-2.amazonaws.com",
+ "s3-accesspoint.ap-south-2.amazonaws.com",
+ "s3-object-lambda.ap-south-2.amazonaws.com",
+ "s3-website.ap-south-2.amazonaws.com",
"s3.dualstack.ap-southeast-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com",
+ "s3-website.dualstack.ap-southeast-1.amazonaws.com",
+ "s3.ap-southeast-1.amazonaws.com",
+ "s3-accesspoint.ap-southeast-1.amazonaws.com",
+ "s3-object-lambda.ap-southeast-1.amazonaws.com",
+ "s3-website.ap-southeast-1.amazonaws.com",
"s3.dualstack.ap-southeast-2.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com",
+ "s3-website.dualstack.ap-southeast-2.amazonaws.com",
+ "s3.ap-southeast-2.amazonaws.com",
+ "s3-accesspoint.ap-southeast-2.amazonaws.com",
+ "s3-object-lambda.ap-southeast-2.amazonaws.com",
+ "s3-website.ap-southeast-2.amazonaws.com",
+ "s3.dualstack.ap-southeast-3.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com",
+ "s3-website.dualstack.ap-southeast-3.amazonaws.com",
+ "s3.ap-southeast-3.amazonaws.com",
+ "s3-accesspoint.ap-southeast-3.amazonaws.com",
+ "s3-object-lambda.ap-southeast-3.amazonaws.com",
+ "s3-website.ap-southeast-3.amazonaws.com",
+ "s3.dualstack.ap-southeast-4.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com",
+ "s3-website.dualstack.ap-southeast-4.amazonaws.com",
+ "s3.ap-southeast-4.amazonaws.com",
+ "s3-accesspoint.ap-southeast-4.amazonaws.com",
+ "s3-object-lambda.ap-southeast-4.amazonaws.com",
+ "s3-website.ap-southeast-4.amazonaws.com",
+ "s3.dualstack.ap-southeast-5.amazonaws.com",
+ "s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com",
+ "s3-website.dualstack.ap-southeast-5.amazonaws.com",
+ "s3.ap-southeast-5.amazonaws.com",
+ "s3-accesspoint.ap-southeast-5.amazonaws.com",
+ "s3-deprecated.ap-southeast-5.amazonaws.com",
+ "s3-object-lambda.ap-southeast-5.amazonaws.com",
+ "s3-website.ap-southeast-5.amazonaws.com",
"s3.dualstack.ca-central-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ca-central-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com",
+ "s3-fips.dualstack.ca-central-1.amazonaws.com",
+ "s3-website.dualstack.ca-central-1.amazonaws.com",
"s3.ca-central-1.amazonaws.com",
+ "s3-accesspoint.ca-central-1.amazonaws.com",
+ "s3-accesspoint-fips.ca-central-1.amazonaws.com",
+ "s3-fips.ca-central-1.amazonaws.com",
+ "s3-object-lambda.ca-central-1.amazonaws.com",
"s3-website.ca-central-1.amazonaws.com",
+ "s3.dualstack.ca-west-1.amazonaws.com",
+ "s3-accesspoint.dualstack.ca-west-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com",
+ "s3-fips.dualstack.ca-west-1.amazonaws.com",
+ "s3-website.dualstack.ca-west-1.amazonaws.com",
+ "s3.ca-west-1.amazonaws.com",
+ "s3-accesspoint.ca-west-1.amazonaws.com",
+ "s3-accesspoint-fips.ca-west-1.amazonaws.com",
+ "s3-fips.ca-west-1.amazonaws.com",
+ "s3-object-lambda.ca-west-1.amazonaws.com",
+ "s3-website.ca-west-1.amazonaws.com",
"s3.dualstack.eu-central-1.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-central-1.amazonaws.com",
+ "s3-website.dualstack.eu-central-1.amazonaws.com",
"s3.eu-central-1.amazonaws.com",
+ "s3-accesspoint.eu-central-1.amazonaws.com",
+ "s3-object-lambda.eu-central-1.amazonaws.com",
"s3-website.eu-central-1.amazonaws.com",
+ "s3.dualstack.eu-central-2.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-central-2.amazonaws.com",
+ "s3-website.dualstack.eu-central-2.amazonaws.com",
+ "s3.eu-central-2.amazonaws.com",
+ "s3-accesspoint.eu-central-2.amazonaws.com",
+ "s3-object-lambda.eu-central-2.amazonaws.com",
+ "s3-website.eu-central-2.amazonaws.com",
+ "s3.dualstack.eu-north-1.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-north-1.amazonaws.com",
+ "s3.eu-north-1.amazonaws.com",
+ "s3-accesspoint.eu-north-1.amazonaws.com",
+ "s3-object-lambda.eu-north-1.amazonaws.com",
+ "s3-website.eu-north-1.amazonaws.com",
+ "s3.dualstack.eu-south-1.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-south-1.amazonaws.com",
+ "s3-website.dualstack.eu-south-1.amazonaws.com",
+ "s3.eu-south-1.amazonaws.com",
+ "s3-accesspoint.eu-south-1.amazonaws.com",
+ "s3-object-lambda.eu-south-1.amazonaws.com",
+ "s3-website.eu-south-1.amazonaws.com",
+ "s3.dualstack.eu-south-2.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-south-2.amazonaws.com",
+ "s3-website.dualstack.eu-south-2.amazonaws.com",
+ "s3.eu-south-2.amazonaws.com",
+ "s3-accesspoint.eu-south-2.amazonaws.com",
+ "s3-object-lambda.eu-south-2.amazonaws.com",
+ "s3-website.eu-south-2.amazonaws.com",
"s3.dualstack.eu-west-1.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-west-1.amazonaws.com",
+ "s3-website.dualstack.eu-west-1.amazonaws.com",
+ "s3.eu-west-1.amazonaws.com",
+ "s3-accesspoint.eu-west-1.amazonaws.com",
+ "s3-deprecated.eu-west-1.amazonaws.com",
+ "s3-object-lambda.eu-west-1.amazonaws.com",
+ "s3-website.eu-west-1.amazonaws.com",
"s3.dualstack.eu-west-2.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-west-2.amazonaws.com",
"s3.eu-west-2.amazonaws.com",
+ "s3-accesspoint.eu-west-2.amazonaws.com",
+ "s3-object-lambda.eu-west-2.amazonaws.com",
"s3-website.eu-west-2.amazonaws.com",
"s3.dualstack.eu-west-3.amazonaws.com",
+ "s3-accesspoint.dualstack.eu-west-3.amazonaws.com",
+ "s3-website.dualstack.eu-west-3.amazonaws.com",
"s3.eu-west-3.amazonaws.com",
+ "s3-accesspoint.eu-west-3.amazonaws.com",
+ "s3-object-lambda.eu-west-3.amazonaws.com",
"s3-website.eu-west-3.amazonaws.com",
+ "s3.dualstack.il-central-1.amazonaws.com",
+ "s3-accesspoint.dualstack.il-central-1.amazonaws.com",
+ "s3-website.dualstack.il-central-1.amazonaws.com",
+ "s3.il-central-1.amazonaws.com",
+ "s3-accesspoint.il-central-1.amazonaws.com",
+ "s3-object-lambda.il-central-1.amazonaws.com",
+ "s3-website.il-central-1.amazonaws.com",
+ "s3.dualstack.me-central-1.amazonaws.com",
+ "s3-accesspoint.dualstack.me-central-1.amazonaws.com",
+ "s3-website.dualstack.me-central-1.amazonaws.com",
+ "s3.me-central-1.amazonaws.com",
+ "s3-accesspoint.me-central-1.amazonaws.com",
+ "s3-object-lambda.me-central-1.amazonaws.com",
+ "s3-website.me-central-1.amazonaws.com",
+ "s3.dualstack.me-south-1.amazonaws.com",
+ "s3-accesspoint.dualstack.me-south-1.amazonaws.com",
+ "s3.me-south-1.amazonaws.com",
+ "s3-accesspoint.me-south-1.amazonaws.com",
+ "s3-object-lambda.me-south-1.amazonaws.com",
+ "s3-website.me-south-1.amazonaws.com",
"s3.amazonaws.com",
+ "s3-1.amazonaws.com",
+ "s3-ap-east-1.amazonaws.com",
"s3-ap-northeast-1.amazonaws.com",
"s3-ap-northeast-2.amazonaws.com",
+ "s3-ap-northeast-3.amazonaws.com",
"s3-ap-south-1.amazonaws.com",
"s3-ap-southeast-1.amazonaws.com",
"s3-ap-southeast-2.amazonaws.com",
"s3-ca-central-1.amazonaws.com",
"s3-eu-central-1.amazonaws.com",
+ "s3-eu-north-1.amazonaws.com",
"s3-eu-west-1.amazonaws.com",
"s3-eu-west-2.amazonaws.com",
"s3-eu-west-3.amazonaws.com",
"s3-external-1.amazonaws.com",
+ "s3-fips-us-gov-east-1.amazonaws.com",
"s3-fips-us-gov-west-1.amazonaws.com",
+ "mrap.accesspoint.s3-global.amazonaws.com",
+ "s3-me-south-1.amazonaws.com",
"s3-sa-east-1.amazonaws.com",
"s3-us-east-2.amazonaws.com",
+ "s3-us-gov-east-1.amazonaws.com",
"s3-us-gov-west-1.amazonaws.com",
"s3-us-west-1.amazonaws.com",
"s3-us-west-2.amazonaws.com",
@@ -6993,18 +7371,181 @@ var rules = [...]string{
"s3-website-eu-west-1.amazonaws.com",
"s3-website-sa-east-1.amazonaws.com",
"s3-website-us-east-1.amazonaws.com",
+ "s3-website-us-gov-west-1.amazonaws.com",
"s3-website-us-west-1.amazonaws.com",
"s3-website-us-west-2.amazonaws.com",
"s3.dualstack.sa-east-1.amazonaws.com",
+ "s3-accesspoint.dualstack.sa-east-1.amazonaws.com",
+ "s3-website.dualstack.sa-east-1.amazonaws.com",
+ "s3.sa-east-1.amazonaws.com",
+ "s3-accesspoint.sa-east-1.amazonaws.com",
+ "s3-object-lambda.sa-east-1.amazonaws.com",
+ "s3-website.sa-east-1.amazonaws.com",
"s3.dualstack.us-east-1.amazonaws.com",
+ "s3-accesspoint.dualstack.us-east-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com",
+ "s3-fips.dualstack.us-east-1.amazonaws.com",
+ "s3-website.dualstack.us-east-1.amazonaws.com",
+ "s3.us-east-1.amazonaws.com",
+ "s3-accesspoint.us-east-1.amazonaws.com",
+ "s3-accesspoint-fips.us-east-1.amazonaws.com",
+ "s3-deprecated.us-east-1.amazonaws.com",
+ "s3-fips.us-east-1.amazonaws.com",
+ "s3-object-lambda.us-east-1.amazonaws.com",
+ "s3-website.us-east-1.amazonaws.com",
"s3.dualstack.us-east-2.amazonaws.com",
+ "s3-accesspoint.dualstack.us-east-2.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com",
+ "s3-fips.dualstack.us-east-2.amazonaws.com",
+ "s3-website.dualstack.us-east-2.amazonaws.com",
"s3.us-east-2.amazonaws.com",
+ "s3-accesspoint.us-east-2.amazonaws.com",
+ "s3-accesspoint-fips.us-east-2.amazonaws.com",
+ "s3-deprecated.us-east-2.amazonaws.com",
+ "s3-fips.us-east-2.amazonaws.com",
+ "s3-object-lambda.us-east-2.amazonaws.com",
"s3-website.us-east-2.amazonaws.com",
+ "s3.dualstack.us-gov-east-1.amazonaws.com",
+ "s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com",
+ "s3-fips.dualstack.us-gov-east-1.amazonaws.com",
+ "s3.us-gov-east-1.amazonaws.com",
+ "s3-accesspoint.us-gov-east-1.amazonaws.com",
+ "s3-accesspoint-fips.us-gov-east-1.amazonaws.com",
+ "s3-fips.us-gov-east-1.amazonaws.com",
+ "s3-object-lambda.us-gov-east-1.amazonaws.com",
+ "s3-website.us-gov-east-1.amazonaws.com",
+ "s3.dualstack.us-gov-west-1.amazonaws.com",
+ "s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com",
+ "s3-fips.dualstack.us-gov-west-1.amazonaws.com",
+ "s3.us-gov-west-1.amazonaws.com",
+ "s3-accesspoint.us-gov-west-1.amazonaws.com",
+ "s3-accesspoint-fips.us-gov-west-1.amazonaws.com",
+ "s3-fips.us-gov-west-1.amazonaws.com",
+ "s3-object-lambda.us-gov-west-1.amazonaws.com",
+ "s3-website.us-gov-west-1.amazonaws.com",
+ "s3.dualstack.us-west-1.amazonaws.com",
+ "s3-accesspoint.dualstack.us-west-1.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com",
+ "s3-fips.dualstack.us-west-1.amazonaws.com",
+ "s3-website.dualstack.us-west-1.amazonaws.com",
+ "s3.us-west-1.amazonaws.com",
+ "s3-accesspoint.us-west-1.amazonaws.com",
+ "s3-accesspoint-fips.us-west-1.amazonaws.com",
+ "s3-fips.us-west-1.amazonaws.com",
+ "s3-object-lambda.us-west-1.amazonaws.com",
+ "s3-website.us-west-1.amazonaws.com",
+ "s3.dualstack.us-west-2.amazonaws.com",
+ "s3-accesspoint.dualstack.us-west-2.amazonaws.com",
+ "s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com",
+ "s3-fips.dualstack.us-west-2.amazonaws.com",
+ "s3-website.dualstack.us-west-2.amazonaws.com",
+ "s3.us-west-2.amazonaws.com",
+ "s3-accesspoint.us-west-2.amazonaws.com",
+ "s3-accesspoint-fips.us-west-2.amazonaws.com",
+ "s3-deprecated.us-west-2.amazonaws.com",
+ "s3-fips.us-west-2.amazonaws.com",
+ "s3-object-lambda.us-west-2.amazonaws.com",
+ "s3-website.us-west-2.amazonaws.com",
+ "labeling.ap-northeast-1.sagemaker.aws",
+ "labeling.ap-northeast-2.sagemaker.aws",
+ "labeling.ap-south-1.sagemaker.aws",
+ "labeling.ap-southeast-1.sagemaker.aws",
+ "labeling.ap-southeast-2.sagemaker.aws",
+ "labeling.ca-central-1.sagemaker.aws",
+ "labeling.eu-central-1.sagemaker.aws",
+ "labeling.eu-west-1.sagemaker.aws",
+ "labeling.eu-west-2.sagemaker.aws",
+ "labeling.us-east-1.sagemaker.aws",
+ "labeling.us-east-2.sagemaker.aws",
+ "labeling.us-west-2.sagemaker.aws",
+ "notebook.af-south-1.sagemaker.aws",
+ "notebook.ap-east-1.sagemaker.aws",
+ "notebook.ap-northeast-1.sagemaker.aws",
+ "notebook.ap-northeast-2.sagemaker.aws",
+ "notebook.ap-northeast-3.sagemaker.aws",
+ "notebook.ap-south-1.sagemaker.aws",
+ "notebook.ap-south-2.sagemaker.aws",
+ "notebook.ap-southeast-1.sagemaker.aws",
+ "notebook.ap-southeast-2.sagemaker.aws",
+ "notebook.ap-southeast-3.sagemaker.aws",
+ "notebook.ap-southeast-4.sagemaker.aws",
+ "notebook.ca-central-1.sagemaker.aws",
+ "notebook-fips.ca-central-1.sagemaker.aws",
+ "notebook.ca-west-1.sagemaker.aws",
+ "notebook-fips.ca-west-1.sagemaker.aws",
+ "notebook.eu-central-1.sagemaker.aws",
+ "notebook.eu-central-2.sagemaker.aws",
+ "notebook.eu-north-1.sagemaker.aws",
+ "notebook.eu-south-1.sagemaker.aws",
+ "notebook.eu-south-2.sagemaker.aws",
+ "notebook.eu-west-1.sagemaker.aws",
+ "notebook.eu-west-2.sagemaker.aws",
+ "notebook.eu-west-3.sagemaker.aws",
+ "notebook.il-central-1.sagemaker.aws",
+ "notebook.me-central-1.sagemaker.aws",
+ "notebook.me-south-1.sagemaker.aws",
+ "notebook.sa-east-1.sagemaker.aws",
+ "notebook.us-east-1.sagemaker.aws",
+ "notebook-fips.us-east-1.sagemaker.aws",
+ "notebook.us-east-2.sagemaker.aws",
+ "notebook-fips.us-east-2.sagemaker.aws",
+ "notebook.us-gov-east-1.sagemaker.aws",
+ "notebook-fips.us-gov-east-1.sagemaker.aws",
+ "notebook.us-gov-west-1.sagemaker.aws",
+ "notebook-fips.us-gov-west-1.sagemaker.aws",
+ "notebook.us-west-1.sagemaker.aws",
+ "notebook-fips.us-west-1.sagemaker.aws",
+ "notebook.us-west-2.sagemaker.aws",
+ "notebook-fips.us-west-2.sagemaker.aws",
+ "notebook.cn-north-1.sagemaker.com.cn",
+ "notebook.cn-northwest-1.sagemaker.com.cn",
+ "studio.af-south-1.sagemaker.aws",
+ "studio.ap-east-1.sagemaker.aws",
+ "studio.ap-northeast-1.sagemaker.aws",
+ "studio.ap-northeast-2.sagemaker.aws",
+ "studio.ap-northeast-3.sagemaker.aws",
+ "studio.ap-south-1.sagemaker.aws",
+ "studio.ap-southeast-1.sagemaker.aws",
+ "studio.ap-southeast-2.sagemaker.aws",
+ "studio.ap-southeast-3.sagemaker.aws",
+ "studio.ca-central-1.sagemaker.aws",
+ "studio.eu-central-1.sagemaker.aws",
+ "studio.eu-central-2.sagemaker.aws",
+ "studio.eu-north-1.sagemaker.aws",
+ "studio.eu-south-1.sagemaker.aws",
+ "studio.eu-south-2.sagemaker.aws",
+ "studio.eu-west-1.sagemaker.aws",
+ "studio.eu-west-2.sagemaker.aws",
+ "studio.eu-west-3.sagemaker.aws",
+ "studio.il-central-1.sagemaker.aws",
+ "studio.me-central-1.sagemaker.aws",
+ "studio.me-south-1.sagemaker.aws",
+ "studio.sa-east-1.sagemaker.aws",
+ "studio.us-east-1.sagemaker.aws",
+ "studio.us-east-2.sagemaker.aws",
+ "studio.us-gov-east-1.sagemaker.aws",
+ "studio-fips.us-gov-east-1.sagemaker.aws",
+ "studio.us-gov-west-1.sagemaker.aws",
+ "studio-fips.us-gov-west-1.sagemaker.aws",
+ "studio.us-west-1.sagemaker.aws",
+ "studio.us-west-2.sagemaker.aws",
+ "studio.cn-north-1.sagemaker.com.cn",
+ "studio.cn-northwest-1.sagemaker.com.cn",
+ "*.experiments.sagemaker.aws",
"analytics-gateway.ap-northeast-1.amazonaws.com",
+ "analytics-gateway.ap-northeast-2.amazonaws.com",
+ "analytics-gateway.ap-south-1.amazonaws.com",
+ "analytics-gateway.ap-southeast-1.amazonaws.com",
+ "analytics-gateway.ap-southeast-2.amazonaws.com",
+ "analytics-gateway.eu-central-1.amazonaws.com",
"analytics-gateway.eu-west-1.amazonaws.com",
"analytics-gateway.us-east-1.amazonaws.com",
"analytics-gateway.us-east-2.amazonaws.com",
"analytics-gateway.us-west-2.amazonaws.com",
+ "amplifyapp.com",
+ "*.awsapprunner.com",
"webview-assets.aws-cloud9.af-south-1.amazonaws.com",
"vfs.cloud9.af-south-1.amazonaws.com",
"webview-assets.cloud9.af-south-1.amazonaws.com",
@@ -7050,6 +7591,8 @@ var rules = [...]string{
"webview-assets.aws-cloud9.eu-west-3.amazonaws.com",
"vfs.cloud9.eu-west-3.amazonaws.com",
"webview-assets.cloud9.eu-west-3.amazonaws.com",
+ "webview-assets.aws-cloud9.il-central-1.amazonaws.com",
+ "vfs.cloud9.il-central-1.amazonaws.com",
"webview-assets.aws-cloud9.me-south-1.amazonaws.com",
"vfs.cloud9.me-south-1.amazonaws.com",
"webview-assets.cloud9.me-south-1.amazonaws.com",
@@ -7068,39 +7611,59 @@ var rules = [...]string{
"webview-assets.aws-cloud9.us-west-2.amazonaws.com",
"vfs.cloud9.us-west-2.amazonaws.com",
"webview-assets.cloud9.us-west-2.amazonaws.com",
+ "awsapps.com",
"cn-north-1.eb.amazonaws.com.cn",
"cn-northwest-1.eb.amazonaws.com.cn",
"elasticbeanstalk.com",
+ "af-south-1.elasticbeanstalk.com",
+ "ap-east-1.elasticbeanstalk.com",
"ap-northeast-1.elasticbeanstalk.com",
"ap-northeast-2.elasticbeanstalk.com",
"ap-northeast-3.elasticbeanstalk.com",
"ap-south-1.elasticbeanstalk.com",
"ap-southeast-1.elasticbeanstalk.com",
"ap-southeast-2.elasticbeanstalk.com",
+ "ap-southeast-3.elasticbeanstalk.com",
"ca-central-1.elasticbeanstalk.com",
"eu-central-1.elasticbeanstalk.com",
+ "eu-north-1.elasticbeanstalk.com",
+ "eu-south-1.elasticbeanstalk.com",
"eu-west-1.elasticbeanstalk.com",
"eu-west-2.elasticbeanstalk.com",
"eu-west-3.elasticbeanstalk.com",
+ "il-central-1.elasticbeanstalk.com",
+ "me-south-1.elasticbeanstalk.com",
"sa-east-1.elasticbeanstalk.com",
"us-east-1.elasticbeanstalk.com",
"us-east-2.elasticbeanstalk.com",
+ "us-gov-east-1.elasticbeanstalk.com",
"us-gov-west-1.elasticbeanstalk.com",
"us-west-1.elasticbeanstalk.com",
"us-west-2.elasticbeanstalk.com",
"*.elb.amazonaws.com.cn",
"*.elb.amazonaws.com",
"awsglobalaccelerator.com",
+ "*.private.repost.aws",
+ "transfer-webapp.ap-northeast-1.on.aws",
+ "transfer-webapp.ap-southeast-1.on.aws",
+ "transfer-webapp.ap-southeast-2.on.aws",
+ "transfer-webapp.eu-central-1.on.aws",
+ "transfer-webapp.eu-north-1.on.aws",
+ "transfer-webapp.eu-west-1.on.aws",
+ "transfer-webapp.us-east-1.on.aws",
+ "transfer-webapp.us-east-2.on.aws",
+ "transfer-webapp.us-west-2.on.aws",
"eero.online",
"eero-stage.online",
- "t3l3p0rt.net",
- "tele.amune.org",
"apigee.io",
+ "panel.dev",
"siiites.com",
"appspacehosted.com",
"appspaceusercontent.com",
"appudo.net",
"on-aptible.com",
+ "f5.si",
+ "arvanedge.ir",
"user.aseinet.ne.jp",
"gv.vc",
"d.gv.vc",
@@ -7112,19 +7675,14 @@ var rules = [...]string{
"myasustor.com",
"cdn.prod.atlassian-dev.net",
"translated.page",
- "autocode.dev",
+ "myfritz.link",
"myfritz.net",
"onavstack.net",
"*.awdev.ca",
"*.advisor.ws",
"ecommerce-shop.pl",
"b-data.io",
- "backplaneapp.io",
"balena-devices.com",
- "rs.ba",
- "*.banzai.cloud",
- "app.banzaicloud.io",
- "*.backyards.banzaicloud.io",
"base.ec",
"official.ec",
"buyshop.jp",
@@ -7137,11 +7695,12 @@ var rules = [...]string{
"base.shop",
"beagleboard.io",
"*.beget.app",
- "betainabox.com",
+ "pages.gay",
"bnr.la",
"bitbucket.io",
"blackbaudcdn.net",
"of.je",
+ "square.site",
"bluebite.io",
"boomla.net",
"boutir.com",
@@ -7152,69 +7711,96 @@ var rules = [...]string{
"square7.de",
"bplaced.net",
"square7.net",
+ "brave.app",
+ "*.s.brave.app",
+ "brave.io",
+ "*.s.brave.io",
+ "shop.brendly.hr",
"shop.brendly.rs",
"browsersafetymark.io",
+ "radio.am",
+ "radio.fm",
+ "cdn.bubble.io",
+ "bubbleapps.io",
"uk0.bigv.io",
"dh.bytemark.co.uk",
"vm.bytemark.co.uk",
"cafjs.com",
- "mycd.eu",
"canva-apps.cn",
+ "*.my.canvasite.cn",
"canva-apps.com",
+ "*.my.canva.site",
"drr.ac",
"uwu.ai",
"carrd.co",
"crd.co",
"ju.mp",
- "ae.org",
+ "api.gov.uk",
+ "cdn77-storage.com",
+ "rsc.contentproxy9.cz",
+ "r.cdn77.net",
+ "cdn77-ssl.net",
+ "c.cdn77.org",
+ "rsc.cdn77.org",
+ "ssl.origin.cdn77-secure.org",
+ "za.bz",
"br.com",
"cn.com",
- "com.de",
- "com.se",
"de.com",
"eu.com",
- "gb.net",
- "hu.net",
- "jp.net",
"jpn.com",
"mex.com",
"ru.com",
"sa.com",
- "se.net",
"uk.com",
- "uk.net",
"us.com",
- "za.bz",
"za.com",
- "ar.com",
- "hu.com",
- "kr.com",
- "no.com",
- "qc.com",
- "uy.com",
- "africa.com",
- "gr.com",
- "in.net",
- "web.in",
- "us.org",
- "co.com",
- "aus.basketball",
- "nz.basketball",
- "radio.am",
- "radio.fm",
- "c.la",
- "certmgr.org",
+ "com.de",
+ "gb.net",
+ "hu.net",
+ "jp.net",
+ "se.net",
+ "uk.net",
+ "ae.org",
+ "com.se",
"cx.ua",
"discourse.group",
"discourse.team",
- "cleverapps.io",
"clerk.app",
"clerkstage.app",
"*.lcl.dev",
"*.lclstage.dev",
"*.stg.dev",
"*.stgstage.dev",
+ "cleverapps.cc",
+ "*.services.clever-cloud.com",
+ "cleverapps.io",
+ "cleverapps.tech",
"clickrising.net",
+ "cloudns.asia",
+ "cloudns.be",
+ "cloud-ip.biz",
+ "cloudns.biz",
+ "cloudns.cc",
+ "cloudns.ch",
+ "cloudns.cl",
+ "cloudns.club",
+ "dnsabr.com",
+ "ip-ddns.com",
+ "cloudns.cx",
+ "cloudns.eu",
+ "cloudns.in",
+ "cloudns.info",
+ "ddns-ip.net",
+ "dns-cloud.net",
+ "dns-dynamic.net",
+ "cloudns.nz",
+ "cloudns.org",
+ "ip-dynamic.org",
+ "cloudns.ph",
+ "cloudns.pro",
+ "cloudns.pw",
+ "cloudns.us",
"c66.me",
"cloud66.ws",
"cloud66.zone",
@@ -7223,8 +7809,7 @@ var rules = [...]string{
"cloudaccess.host",
"freesite.host",
"cloudaccess.net",
- "cloudcontrolled.com",
- "cloudcontrolapp.com",
+ "cloudbeesusercontent.io",
"*.cloudera.site",
"cf-ipfs.com",
"cloudflare-ipfs.com",
@@ -7232,89 +7817,93 @@ var rules = [...]string{
"pages.dev",
"r2.dev",
"workers.dev",
+ "cloudflare.net",
+ "cdn.cloudflare.net",
+ "cdn.cloudflareanycast.net",
+ "cdn.cloudflarecn.net",
+ "cdn.cloudflareglobal.net",
+ "cust.cloudscale.ch",
+ "objects.lpg.cloudscale.ch",
+ "objects.rma.cloudscale.ch",
"wnext.app",
- "co.ca",
- "*.otap.co",
- "co.cz",
- "c.cdn77.org",
- "cdn77-ssl.net",
- "r.cdn77.net",
- "rsc.cdn77.org",
- "ssl.origin.cdn77-secure.org",
- "cloudns.asia",
- "cloudns.biz",
- "cloudns.club",
- "cloudns.cc",
- "cloudns.eu",
- "cloudns.in",
- "cloudns.info",
- "cloudns.org",
- "cloudns.pro",
- "cloudns.pw",
- "cloudns.us",
"cnpy.gdn",
+ "*.otap.co",
+ "co.ca",
+ "co.com",
"codeberg.page",
+ "csb.app",
+ "preview.csb.app",
"co.nl",
"co.no",
+ "devinapps.com",
+ "staging.devinapps.com",
"webhosting.be",
"hosting-cluster.nl",
+ "ctfcloud.net",
+ "convex.site",
"ac.ru",
"edu.ru",
"gov.ru",
"int.ru",
"mil.ru",
- "test.ru",
"dyn.cosidns.de",
- "dynamisches-dns.de",
"dnsupdater.de",
+ "dynamisches-dns.de",
"internet-dns.de",
"l-o-g-i-n.de",
"dynamic-dns.info",
"feste-ip.net",
"knx-server.net",
"static-access.net",
+ "craft.me",
"realm.cz",
+ "on.crisp.email",
"*.cryptonomic.net",
- "cupcake.is",
- "curv.dev",
- "*.customer-oci.com",
- "*.oci.customer-oci.com",
- "*.ocp.customer-oci.com",
- "*.ocs.customer-oci.com",
+ "cfolks.pl",
"cyon.link",
"cyon.site",
- "fnwk.site",
- "folionetwork.site",
- "platform0.app",
- "daplie.me",
- "localhost.daplie.me",
- "dattolocal.com",
- "dattorelay.com",
- "dattoweb.com",
- "mydatto.com",
- "dattolocal.net",
- "mydatto.net",
"biz.dk",
"co.dk",
"firm.dk",
"reg.dk",
"store.dk",
"dyndns.dappnode.io",
- "*.dapps.earth",
- "*.bzz.dapps.earth",
"builtwithdark.com",
+ "darklang.io",
"demo.datadetect.com",
"instance.datadetect.com",
"edgestack.me",
- "ddns5.com",
+ "dattolocal.com",
+ "dattorelay.com",
+ "dattoweb.com",
+ "mydatto.com",
+ "dattolocal.net",
+ "mydatto.net",
+ "ddnss.de",
+ "dyn.ddnss.de",
+ "dyndns.ddnss.de",
+ "dyn-ip24.de",
+ "dyndns1.de",
+ "home-webserver.de",
+ "dyn.home-webserver.de",
+ "myhome-server.de",
+ "ddnss.org",
"debian.net",
+ "definima.io",
+ "definima.net",
"deno.dev",
"deno-staging.dev",
+ "deno.net",
"dedyn.io",
"deta.app",
"deta.dev",
- "*.rss.my.id",
- "*.diher.solutions",
+ "dfirma.pl",
+ "dkonto.pl",
+ "you2.pl",
+ "ondigitalocean.app",
+ "*.digitaloceanspaces.com",
+ "us.kg",
+ "dpdns.org",
"discordsays.com",
"discordsez.com",
"jozi.biz",
@@ -7324,14 +7913,31 @@ var rules = [...]string{
"drayddns.com",
"shoparena.pl",
"dreamhosters.com",
+ "durumis.com",
"mydrobo.com",
- "drud.io",
- "drud.us",
"duckdns.org",
- "bip.sh",
- "bitbridge.net",
"dy.fi",
"tunk.org",
+ "dyndns.biz",
+ "for-better.biz",
+ "for-more.biz",
+ "for-some.biz",
+ "for-the.biz",
+ "selfip.biz",
+ "webhop.biz",
+ "ftpaccess.cc",
+ "game-server.cc",
+ "myphotos.cc",
+ "scrapping.cc",
+ "blogdns.com",
+ "cechire.com",
+ "dnsalias.com",
+ "dnsdojo.com",
+ "doesntexist.com",
+ "dontexist.com",
+ "doomdns.com",
+ "dyn-o-saur.com",
+ "dynalias.com",
"dyndns-at-home.com",
"dyndns-at-work.com",
"dyndns-blog.com",
@@ -7346,64 +7952,14 @@ var rules = [...]string{
"dyndns-web.com",
"dyndns-wiki.com",
"dyndns-work.com",
- "dyndns.biz",
- "dyndns.info",
- "dyndns.org",
- "dyndns.tv",
- "at-band-camp.net",
- "ath.cx",
- "barrel-of-knowledge.info",
- "barrell-of-knowledge.info",
- "better-than.tv",
- "blogdns.com",
- "blogdns.net",
- "blogdns.org",
- "blogsite.org",
- "boldlygoingnowhere.org",
- "broke-it.net",
- "buyshouses.net",
- "cechire.com",
- "dnsalias.com",
- "dnsalias.net",
- "dnsalias.org",
- "dnsdojo.com",
- "dnsdojo.net",
- "dnsdojo.org",
- "does-it.net",
- "doesntexist.com",
- "doesntexist.org",
- "dontexist.com",
- "dontexist.net",
- "dontexist.org",
- "doomdns.com",
- "doomdns.org",
- "dvrdns.org",
- "dyn-o-saur.com",
- "dynalias.com",
- "dynalias.net",
- "dynalias.org",
- "dynathome.net",
- "dyndns.ws",
- "endofinternet.net",
- "endofinternet.org",
- "endoftheinternet.org",
"est-a-la-maison.com",
"est-a-la-masion.com",
"est-le-patron.com",
"est-mon-blogueur.com",
- "for-better.biz",
- "for-more.biz",
- "for-our.info",
- "for-some.biz",
- "for-the.biz",
- "forgot.her.name",
- "forgot.his.name",
"from-ak.com",
"from-al.com",
"from-ar.com",
- "from-az.net",
"from-ca.com",
- "from-co.net",
"from-ct.com",
"from-dc.com",
"from-de.com",
@@ -7416,10 +7972,8 @@ var rules = [...]string{
"from-in.com",
"from-ks.com",
"from-ky.com",
- "from-la.net",
"from-ma.com",
"from-md.com",
- "from-me.org",
"from-mi.com",
"from-mn.com",
"from-mo.com",
@@ -7432,7 +7986,6 @@ var rules = [...]string{
"from-nj.com",
"from-nm.com",
"from-nv.com",
- "from-ny.net",
"from-oh.com",
"from-ok.com",
"from-or.com",
@@ -7450,45 +8003,18 @@ var rules = [...]string{
"from-wi.com",
"from-wv.com",
"from-wy.com",
- "ftpaccess.cc",
- "fuettertdasnetz.de",
- "game-host.org",
- "game-server.cc",
"getmyip.com",
- "gets-it.net",
- "go.dyndns.org",
"gotdns.com",
- "gotdns.org",
- "groks-the.info",
- "groks-this.info",
- "ham-radio-op.net",
- "here-for-more.info",
"hobby-site.com",
- "hobby-site.org",
- "home.dyndns.org",
- "homedns.org",
- "homeftp.net",
- "homeftp.org",
- "homeip.net",
"homelinux.com",
- "homelinux.net",
- "homelinux.org",
"homeunix.com",
- "homeunix.net",
- "homeunix.org",
"iamallama.com",
- "in-the-band.net",
"is-a-anarchist.com",
"is-a-blogger.com",
"is-a-bookkeeper.com",
- "is-a-bruinsfan.org",
"is-a-bulls-fan.com",
- "is-a-candidate.org",
"is-a-caterer.com",
- "is-a-celticsfan.org",
"is-a-chef.com",
- "is-a-chef.net",
- "is-a-chef.org",
"is-a-conservative.com",
"is-a-cpa.com",
"is-a-cubicle-slave.com",
@@ -7497,31 +8023,25 @@ var rules = [...]string{
"is-a-doctor.com",
"is-a-financialadvisor.com",
"is-a-geek.com",
- "is-a-geek.net",
- "is-a-geek.org",
"is-a-green.com",
"is-a-guru.com",
"is-a-hard-worker.com",
"is-a-hunter.com",
- "is-a-knight.org",
"is-a-landscaper.com",
"is-a-lawyer.com",
"is-a-liberal.com",
"is-a-libertarian.com",
- "is-a-linux-user.org",
"is-a-llama.com",
"is-a-musician.com",
"is-a-nascarfan.com",
"is-a-nurse.com",
"is-a-painter.com",
- "is-a-patsfan.org",
"is-a-personaltrainer.com",
"is-a-photographer.com",
"is-a-player.com",
"is-a-republican.com",
"is-a-rockstar.com",
"is-a-socialist.com",
- "is-a-soxfan.org",
"is-a-student.com",
"is-a-teacher.com",
"is-a-techie.com",
@@ -7533,98 +8053,150 @@ var rules = [...]string{
"is-an-artist.com",
"is-an-engineer.com",
"is-an-entertainer.com",
- "is-by.us",
"is-certified.com",
- "is-found.org",
"is-gone.com",
"is-into-anime.com",
"is-into-cars.com",
"is-into-cartoons.com",
"is-into-games.com",
"is-leet.com",
- "is-lost.org",
"is-not-certified.com",
- "is-saved.org",
"is-slick.com",
"is-uberleet.com",
- "is-very-bad.org",
- "is-very-evil.org",
- "is-very-good.org",
- "is-very-nice.org",
- "is-very-sweet.org",
"is-with-theband.com",
"isa-geek.com",
- "isa-geek.net",
- "isa-geek.org",
"isa-hockeynut.com",
"issmarterthanyou.com",
+ "likes-pie.com",
+ "likescandy.com",
+ "neat-url.com",
+ "saves-the-whales.com",
+ "selfip.com",
+ "sells-for-less.com",
+ "sells-for-u.com",
+ "servebbs.com",
+ "simple-url.com",
+ "space-to-rent.com",
+ "teaches-yoga.com",
+ "writesthisblog.com",
+ "ath.cx",
+ "fuettertdasnetz.de",
"isteingeek.de",
"istmein.de",
- "kicks-ass.net",
- "kicks-ass.org",
- "knowsitall.info",
- "land-4-sale.us",
"lebtimnetz.de",
"leitungsen.de",
- "likes-pie.com",
- "likescandy.com",
+ "traeumtgerade.de",
+ "barrel-of-knowledge.info",
+ "barrell-of-knowledge.info",
+ "dyndns.info",
+ "for-our.info",
+ "groks-the.info",
+ "groks-this.info",
+ "here-for-more.info",
+ "knowsitall.info",
+ "selfip.info",
+ "webhop.info",
+ "forgot.her.name",
+ "forgot.his.name",
+ "at-band-camp.net",
+ "blogdns.net",
+ "broke-it.net",
+ "buyshouses.net",
+ "dnsalias.net",
+ "dnsdojo.net",
+ "does-it.net",
+ "dontexist.net",
+ "dynalias.net",
+ "dynathome.net",
+ "endofinternet.net",
+ "from-az.net",
+ "from-co.net",
+ "from-la.net",
+ "from-ny.net",
+ "gets-it.net",
+ "ham-radio-op.net",
+ "homeftp.net",
+ "homeip.net",
+ "homelinux.net",
+ "homeunix.net",
+ "in-the-band.net",
+ "is-a-chef.net",
+ "is-a-geek.net",
+ "isa-geek.net",
+ "kicks-ass.net",
+ "office-on-the.net",
+ "podzone.net",
+ "scrapper-site.net",
+ "selfip.net",
+ "sells-it.net",
+ "servebbs.net",
+ "serveftp.net",
+ "thruhere.net",
+ "webhop.net",
"merseine.nu",
"mine.nu",
+ "shacknet.nu",
+ "blogdns.org",
+ "blogsite.org",
+ "boldlygoingnowhere.org",
+ "dnsalias.org",
+ "dnsdojo.org",
+ "doesntexist.org",
+ "dontexist.org",
+ "doomdns.org",
+ "dvrdns.org",
+ "dynalias.org",
+ "dyndns.org",
+ "go.dyndns.org",
+ "home.dyndns.org",
+ "endofinternet.org",
+ "endoftheinternet.org",
+ "from-me.org",
+ "game-host.org",
+ "gotdns.org",
+ "hobby-site.org",
+ "homedns.org",
+ "homeftp.org",
+ "homelinux.org",
+ "homeunix.org",
+ "is-a-bruinsfan.org",
+ "is-a-candidate.org",
+ "is-a-celticsfan.org",
+ "is-a-chef.org",
+ "is-a-geek.org",
+ "is-a-knight.org",
+ "is-a-linux-user.org",
+ "is-a-patsfan.org",
+ "is-a-soxfan.org",
+ "is-found.org",
+ "is-lost.org",
+ "is-saved.org",
+ "is-very-bad.org",
+ "is-very-evil.org",
+ "is-very-good.org",
+ "is-very-nice.org",
+ "is-very-sweet.org",
+ "isa-geek.org",
+ "kicks-ass.org",
"misconfused.org",
- "mypets.ws",
- "myphotos.cc",
- "neat-url.com",
- "office-on-the.net",
- "on-the-web.tv",
- "podzone.net",
"podzone.org",
"readmyblog.org",
- "saves-the-whales.com",
- "scrapper-site.net",
- "scrapping.cc",
- "selfip.biz",
- "selfip.com",
- "selfip.info",
- "selfip.net",
"selfip.org",
- "sells-for-less.com",
- "sells-for-u.com",
- "sells-it.net",
"sellsyourhome.org",
- "servebbs.com",
- "servebbs.net",
"servebbs.org",
- "serveftp.net",
"serveftp.org",
"servegame.org",
- "shacknet.nu",
- "simple-url.com",
- "space-to-rent.com",
"stuff-4-sale.org",
- "stuff-4-sale.us",
- "teaches-yoga.com",
- "thruhere.net",
- "traeumtgerade.de",
- "webhop.biz",
- "webhop.info",
- "webhop.net",
"webhop.org",
+ "better-than.tv",
+ "dyndns.tv",
+ "on-the-web.tv",
"worse-than.tv",
- "writesthisblog.com",
- "ddnss.de",
- "dyn.ddnss.de",
- "dyndns.ddnss.de",
- "dyndns1.de",
- "dyn-ip24.de",
- "home-webserver.de",
- "dyn.home-webserver.de",
- "myhome-server.de",
- "ddnss.org",
- "definima.net",
- "definima.io",
- "ondigitalocean.app",
- "*.digitaloceanspaces.com",
- "bci.dnstrace.pro",
+ "is-by.us",
+ "land-4-sale.us",
+ "stuff-4-sale.us",
+ "dyndns.ws",
+ "mypets.ws",
"ddnsfree.com",
"ddnsgeek.com",
"giize.com",
@@ -7641,11 +8213,18 @@ var rules = [...]string{
"mywire.org",
"webredirect.org",
"myddns.rocks",
- "blogsite.xyz",
"dynv6.net",
"e4.cz",
"easypanel.app",
"easypanel.host",
+ "*.ewp.live",
+ "twmail.cc",
+ "twmail.net",
+ "twmail.org",
+ "mymailer.com.tw",
+ "url.tw",
+ "at.emf.camp",
+ "rt.ht",
"elementor.cloud",
"elementor.cool",
"en-root.fr",
@@ -7653,8 +8232,6 @@ var rules = [...]string{
"tuleap-partners.com",
"encr.app",
"encoreapi.com",
- "onred.one",
- "staging.onred.one",
"eu.encoway.cloud",
"eu.org",
"al.eu.org",
@@ -7690,7 +8267,6 @@ var rules = [...]string{
"lt.eu.org",
"lu.eu.org",
"lv.eu.org",
- "mc.eu.org",
"me.eu.org",
"mk.eu.org",
"mt.eu.org",
@@ -7700,10 +8276,8 @@ var rules = [...]string{
"nl.eu.org",
"no.eu.org",
"nz.eu.org",
- "paris.eu.org",
"pl.eu.org",
"pt.eu.org",
- "q-a.eu.org",
"ro.eu.org",
"ru.eu.org",
"se.eu.org",
@@ -7721,13 +8295,11 @@ var rules = [...]string{
"us-2.evennode.com",
"us-3.evennode.com",
"us-4.evennode.com",
- "twmail.cc",
- "twmail.net",
- "twmail.org",
- "mymailer.com.tw",
- "url.tw",
+ "relay.evervault.app",
+ "relay.evervault.dev",
+ "expo.app",
+ "staging.expo.app",
"onfabrica.com",
- "apps.fbsbx.com",
"ru.net",
"adygeya.ru",
"bashkiria.ru",
@@ -7805,8 +8377,6 @@ var rules = [...]string{
"edgecompute.app",
"fastly-edge.com",
"fastly-terrarium.com",
- "fastlylb.net",
- "map.fastlylb.net",
"freetls.fastly.net",
"map.fastly.net",
"a.prod.fastly.net",
@@ -7814,49 +8384,42 @@ var rules = [...]string{
"a.ssl.fastly.net",
"b.ssl.fastly.net",
"global.ssl.fastly.net",
+ "fastlylb.net",
+ "map.fastlylb.net",
"*.user.fm",
"fastvps-server.com",
"fastvps.host",
"myfast.host",
"fastvps.site",
"myfast.space",
+ "conn.uk",
+ "copro.uk",
+ "hosp.uk",
"fedorainfracloud.org",
"fedorapeople.org",
"cloud.fedoraproject.org",
"app.os.fedoraproject.org",
"app.os.stg.fedoraproject.org",
- "conn.uk",
- "copro.uk",
- "hosp.uk",
"mydobiss.com",
"fh-muenster.io",
"filegear.me",
- "filegear-au.me",
- "filegear-de.me",
- "filegear-gb.me",
- "filegear-ie.me",
- "filegear-jp.me",
- "filegear-sg.me",
"firebaseapp.com",
- "fireweb.app",
- "flap.id",
- "onflashdrive.app",
"fldrv.com",
+ "on-fleek.app",
+ "flutterflow.app",
"fly.dev",
- "edgeapp.net",
"shw.io",
- "flynnhosting.net",
+ "edgeapp.net",
"forgeblocks.com",
"id.forgerock.io",
+ "framer.ai",
"framer.app",
"framercanvas.com",
"framer.media",
"framer.photos",
"framer.website",
"framer.wiki",
- "*.frusky.de",
- "ravpage.co.il",
- "0e.vc",
+ "*.0e.vc",
"freebox-os.com",
"freeboxos.com",
"fbx-os.fr",
@@ -7865,7 +8428,23 @@ var rules = [...]string{
"freeboxos.fr",
"freedesktop.org",
"freemyip.com",
+ "*.frusky.de",
"wien.funkfeuer.at",
+ "daemon.asia",
+ "dix.asia",
+ "mydns.bz",
+ "0am.jp",
+ "0g0.jp",
+ "0j0.jp",
+ "0t0.jp",
+ "mydns.jp",
+ "pgw.jp",
+ "wjg.jp",
+ "keyword-on.net",
+ "live-on.net",
+ "server-on.net",
+ "mydns.tw",
+ "mydns.vc",
"*.futurecms.at",
"*.ex.futurecms.at",
"*.in.futurecms.at",
@@ -7874,6 +8453,9 @@ var rules = [...]string{
"*.ex.ortsinfo.at",
"*.kunden.ortsinfo.at",
"*.statics.cloud",
+ "aliases121.com",
+ "campaign.gov.uk",
+ "service.gov.uk",
"independent-commission.uk",
"independent-inquest.uk",
"independent-inquiry.uk",
@@ -7881,16 +8463,11 @@ var rules = [...]string{
"independent-review.uk",
"public-inquiry.uk",
"royal-commission.uk",
- "campaign.gov.uk",
- "service.gov.uk",
- "api.gov.uk",
"gehirn.ne.jp",
"usercontent.jp",
"gentapps.com",
"gentlentapis.com",
- "lab.ms",
"cdn-edges.net",
- "ghost.io",
"gsj.bz",
"githubusercontent.com",
"githubpreview.dev",
@@ -8009,23 +8586,19 @@ var rules = [...]string{
"whitesnow.jp",
"zombie.jp",
"heteml.net",
- "cloudapps.digital",
- "london.cloudapps.digital",
- "pymnt.uk",
- "homeoffice.gov.uk",
- "ro.im",
+ "graphic.design",
"goip.de",
- "run.app",
- "a.run.app",
+ "*.hosted.app",
+ "*.run.app",
"web.app",
"*.0emm.com",
"appspot.com",
"*.r.appspot.com",
+ "blogspot.com",
"codespot.com",
"googleapis.com",
"googlecode.com",
"pagespeedmobilizer.com",
- "publishproxy.com",
"withgoogle.com",
"withyoutube.com",
"*.gateway.dev",
@@ -8033,126 +8606,71 @@ var rules = [...]string{
"translate.goog",
"*.usercontent.goog",
"cloudfunctions.net",
- "blogspot.ae",
- "blogspot.al",
- "blogspot.am",
- "blogspot.ba",
- "blogspot.be",
- "blogspot.bg",
- "blogspot.bj",
- "blogspot.ca",
- "blogspot.cf",
- "blogspot.ch",
- "blogspot.cl",
- "blogspot.co.at",
- "blogspot.co.id",
- "blogspot.co.il",
- "blogspot.co.ke",
- "blogspot.co.nz",
- "blogspot.co.uk",
- "blogspot.co.za",
- "blogspot.com",
- "blogspot.com.ar",
- "blogspot.com.au",
- "blogspot.com.br",
- "blogspot.com.by",
- "blogspot.com.co",
- "blogspot.com.cy",
- "blogspot.com.ee",
- "blogspot.com.eg",
- "blogspot.com.es",
- "blogspot.com.mt",
- "blogspot.com.ng",
- "blogspot.com.tr",
- "blogspot.com.uy",
- "blogspot.cv",
- "blogspot.cz",
- "blogspot.de",
- "blogspot.dk",
- "blogspot.fi",
- "blogspot.fr",
- "blogspot.gr",
- "blogspot.hk",
- "blogspot.hr",
- "blogspot.hu",
- "blogspot.ie",
- "blogspot.in",
- "blogspot.is",
- "blogspot.it",
- "blogspot.jp",
- "blogspot.kr",
- "blogspot.li",
- "blogspot.lt",
- "blogspot.lu",
- "blogspot.md",
- "blogspot.mk",
- "blogspot.mr",
- "blogspot.mx",
- "blogspot.my",
- "blogspot.nl",
- "blogspot.no",
- "blogspot.pe",
- "blogspot.pt",
- "blogspot.qa",
- "blogspot.re",
- "blogspot.ro",
- "blogspot.rs",
- "blogspot.ru",
- "blogspot.se",
- "blogspot.sg",
- "blogspot.si",
- "blogspot.sk",
- "blogspot.sn",
- "blogspot.td",
- "blogspot.tw",
- "blogspot.ug",
- "blogspot.vn",
"goupile.fr",
+ "pymnt.uk",
+ "cloudapps.digital",
+ "london.cloudapps.digital",
"gov.nl",
- "awsmppl.com",
+ "grafana-dev.net",
+ "grayjayleagues.com",
"xn--gnstigbestellen-zvb.de",
"xn--gnstigliefern-wob.de",
- "fin.ci",
- "free.hr",
- "caa.li",
- "ua.rs",
- "conf.se",
- "hs.zone",
- "hs.run",
+ "xn--hkkinen-5wa.fi",
+ "hrsn.dev",
"hashbang.sh",
"hasura.app",
"hasura-app.io",
+ "hatenablog.com",
+ "hatenadiary.com",
+ "hateblo.jp",
+ "hatenablog.jp",
+ "hatenadiary.jp",
+ "hatenadiary.org",
"pages.it.hs-heilbronn.de",
+ "pages-research.it.hs-heilbronn.de",
+ "heiyu.space",
+ "helioho.st",
+ "heliohost.us",
"hepforge.org",
"herokuapp.com",
- "herokussl.com",
+ "heyflow.page",
+ "heyflow.site",
"ravendb.cloud",
"ravendb.community",
- "ravendb.me",
"development.run",
"ravendb.run",
"homesklep.pl",
- "secaas.hk",
+ "*.kin.one",
+ "*.id.pub",
+ "*.kin.pub",
"hoplix.shop",
"orx.biz",
"biz.gl",
+ "biz.ng",
+ "co.biz.ng",
+ "dl.biz.ng",
+ "go.biz.ng",
+ "lg.biz.ng",
+ "on.biz.ng",
"col.ng",
"firm.ng",
"gen.ng",
"ltd.ng",
"ngo.ng",
- "edu.scot",
- "sch.so",
+ "plc.ng",
"ie.ua",
"hostyhosting.io",
- "xn--hkkinen-5wa.fi",
+ "hf.space",
+ "static.hf.space",
+ "hypernode.io",
+ "iobb.net",
+ "co.cz",
"*.moonscale.io",
"moonscale.net",
+ "gr.com",
"iki.fi",
"ibxos.it",
"iliadboxos.it",
- "impertrixcdn.com",
- "impertrix.com",
"smushcdn.com",
"wphostedmail.com",
"wpmucdn.com",
@@ -8163,11 +8681,12 @@ var rules = [...]string{
"in-brb.de",
"in-butter.de",
"in-dsl.de",
- "in-dsl.net",
- "in-dsl.org",
"in-vpn.de",
+ "in-dsl.net",
"in-vpn.net",
+ "in-dsl.org",
"in-vpn.org",
+ "oninferno.net",
"biz.at",
"info.at",
"info.cx",
@@ -8200,18 +8719,33 @@ var rules = [...]string{
"to.leg.br",
"pixolino.com",
"na4u.ru",
+ "botdash.app",
+ "botdash.dev",
+ "botdash.gg",
+ "botdash.net",
+ "botda.sh",
+ "botdash.xyz",
+ "apps-1and1.com",
+ "live-website.com",
+ "apps-1and1.net",
+ "websitebuilder.online",
+ "app-ionos.space",
"iopsys.se",
+ "*.inbrowser.dev",
+ "*.dweb.link",
+ "*.inbrowser.link",
"ipifony.net",
+ "ir.md",
+ "is-a-good.dev",
+ "is-a.dev",
"iservschule.de",
"mein-iserv.de",
"schulplattform.de",
"schulserver.de",
"test-iserv.de",
"iserv.dev",
- "iobb.net",
"mel.cloudlets.com.au",
"cloud.interhostsolutions.be",
- "mycloud.by",
"alp1.ae.flow.ch",
"appengine.flow.ch",
"es-1.axarnet.cloud",
@@ -8233,7 +8767,6 @@ var rules = [...]string{
"ch.trendhosting.cloud",
"de.trendhosting.cloud",
"jele.club",
- "amscompute.com",
"dopaas.com",
"paas.hosted-by-previder.com",
"rag-cloud.hosteur.com",
@@ -8241,10 +8774,8 @@ var rules = [...]string{
"jcloud.ik-server.com",
"jcloud-ver-jpc.ik-server.com",
"demo.jelastic.com",
- "kilatiron.com",
"paas.massivegrid.com",
"jed.wafaicloud.com",
- "lon.wafaicloud.com",
"ryd.wafaicloud.com",
"j.scaleforce.com.cy",
"jelastic.dogado.eu",
@@ -8256,18 +8787,14 @@ var rules = [...]string{
"paas.beebyte.io",
"sekd1.beebyteapp.io",
"jele.io",
- "cloud-fr1.unispace.io",
"jc.neen.it",
- "cloud.jelastic.open.tim.it",
"jcloud.kz",
- "upaas.kazteleport.kz",
"cloudjiffy.net",
"fra1-de.cloudjiffy.net",
"west1-us.cloudjiffy.net",
"jls-sto1.elastx.net",
"jls-sto2.elastx.net",
"jls-sto3.elastx.net",
- "faststacks.net",
"fr-1.paas.massivegrid.net",
"lon-1.paas.massivegrid.net",
"lon-2.paas.massivegrid.net",
@@ -8277,11 +8804,9 @@ var rules = [...]string{
"jelastic.saveincloud.net",
"nordeste-idc.saveincloud.net",
"j.scaleforce.net",
- "jelastic.tsukaeru.net",
"sdscloud.pl",
"unicloud.pl",
"mircloud.ru",
- "jelastic.regruhosting.ru",
"enscaled.sg",
"jele.site",
"jelastic.team",
@@ -8295,32 +8820,35 @@ var rules = [...]string{
"*.spectrum.myjino.ru",
"*.vps.myjino.ru",
"jotelulu.cloud",
- "*.triton.zone",
+ "webadorsite.com",
+ "jouwweb.site",
"*.cns.joyent.com",
+ "*.triton.zone",
"js.org",
"kaas.gg",
"khplay.nl",
- "ktistory.com",
"kapsi.fi",
+ "ezproxy.kuleuven.be",
+ "kuleuven.cloud",
"keymachine.de",
"kinghost.net",
"uni5.net",
"knightpoint.systems",
"koobin.events",
- "oya.to",
- "kuleuven.cloud",
- "ezproxy.kuleuven.be",
- "co.krd",
- "edu.krd",
- "krellian.net",
"webthings.io",
+ "krellian.net",
+ "oya.to",
+ "laravel.cloud",
"git-repos.de",
"lcube-server.de",
"svn-repos.de",
"leadpages.co",
"lpages.co",
"lpusercontent.com",
- "lelux.site",
+ "liara.run",
+ "iran.liara.run",
+ "libp2p.direct",
+ "runcontainers.dev",
"co.business",
"co.education",
"co.events",
@@ -8328,23 +8856,35 @@ var rules = [...]string{
"co.network",
"co.place",
"co.technology",
- "app.lmpm.com",
- "linkyard.cloud",
"linkyard-cloud.ch",
+ "linkyard.cloud",
"members.linode.com",
"*.nodebalancer.linode.com",
"*.linodeobjects.com",
"ip.linodeusercontent.com",
"we.bs",
+ "filegear-sg.me",
+ "ggff.net",
"*.user.localcert.dev",
- "localzone.xyz",
+ "localcert.net",
+ "localhostcert.net",
+ "localtonet.com",
+ "*.localto.net",
+ "lodz.pl",
+ "pabianice.pl",
+ "plock.pl",
+ "sieradz.pl",
+ "skierniewice.pl",
+ "zgierz.pl",
"loginline.app",
"loginline.dev",
"loginline.io",
"loginline.services",
"loginline.site",
- "servers.run",
"lohmus.me",
+ "servers.run",
+ "lovable.app",
+ "lovableproject.com",
"krasnik.pl",
"leczna.pl",
"lubartow.pl",
@@ -8355,18 +8895,19 @@ var rules = [...]string{
"lug.org.uk",
"lugs.org.uk",
"barsy.bg",
- "barsy.co.uk",
- "barsyonline.co.uk",
+ "barsy.club",
"barsycenter.com",
"barsyonline.com",
- "barsy.club",
"barsy.de",
+ "barsy.dev",
"barsy.eu",
+ "barsy.gr",
"barsy.in",
"barsy.info",
"barsy.io",
"barsy.me",
"barsy.menu",
+ "barsyonline.menu",
"barsy.mobi",
"barsy.net",
"barsy.online",
@@ -8374,27 +8915,37 @@ var rules = [...]string{
"barsy.pro",
"barsy.pub",
"barsy.ro",
+ "barsy.rs",
"barsy.shop",
+ "barsyonline.shop",
"barsy.site",
+ "barsy.store",
"barsy.support",
"barsy.uk",
+ "barsy.co.uk",
+ "barsyonline.co.uk",
"*.magentosite.cloud",
+ "hb.cldmail.ru",
+ "matlab.cloud",
+ "modelscape.com",
+ "mwcloudnonprod.com",
+ "polyspace.com",
"mayfirst.info",
"mayfirst.org",
- "hb.cldmail.ru",
- "cn.vu",
"mazeplay.com",
- "mcpe.me",
"mcdir.me",
"mcdir.ru",
- "mcpre.ru",
"vps.mcdir.ru",
+ "mcpre.ru",
"mediatech.by",
"mediatech.dev",
"hra.health",
+ "medusajs.app",
"miniserver.com",
"memset.net",
"messerli.app",
+ "atmeta.com",
+ "apps.fbsbx.com",
"*.cloud.metacentrum.cz",
"custom.metacentrum.cz",
"flt.cloud.muni.cz",
@@ -8403,29 +8954,47 @@ var rules = [...]string{
"eu.meteorapp.com",
"co.pl",
"*.azurecontainer.io",
- "azurewebsites.net",
+ "azure-api.net",
"azure-mobile.net",
- "cloudapp.net",
+ "azureedge.net",
+ "azurefd.net",
"azurestaticapps.net",
"1.azurestaticapps.net",
"2.azurestaticapps.net",
"3.azurestaticapps.net",
+ "4.azurestaticapps.net",
+ "5.azurestaticapps.net",
+ "6.azurestaticapps.net",
+ "7.azurestaticapps.net",
"centralus.azurestaticapps.net",
"eastasia.azurestaticapps.net",
"eastus2.azurestaticapps.net",
"westeurope.azurestaticapps.net",
"westus2.azurestaticapps.net",
+ "azurewebsites.net",
+ "cloudapp.net",
+ "trafficmanager.net",
+ "blob.core.windows.net",
+ "servicebus.windows.net",
+ "routingthecloud.com",
+ "sn.mynetname.net",
+ "routingthecloud.net",
+ "routingthecloud.org",
"csx.cc",
- "mintere.site",
- "forte.id",
- "mozilla-iot.org",
+ "mydbserver.com",
+ "webspaceconfig.de",
+ "mittwald.info",
+ "mittwaldserver.info",
+ "typo3server.info",
+ "project.space",
+ "modx.dev",
"bmoattachments.org",
"net.ru",
"org.ru",
"pp.ru",
"hostedpi.com",
- "customer.mythic-beasts.com",
"caracal.mythic-beasts.com",
+ "customer.mythic-beasts.com",
"fentiger.mythic-beasts.com",
"lynx.mythic-beasts.com",
"ocelot.mythic-beasts.com",
@@ -8438,8 +9007,14 @@ var rules = [...]string{
"cust.retrosnub.co.uk",
"ui.nabu.casa",
"cloud.nospamproxy.com",
+ "o365.cloud.nospamproxy.com",
+ "netlib.re",
+ "netfy.app",
"netlify.app",
"4u.com",
+ "nfshost.com",
+ "ipfs.nftstorage.link",
+ "ngo.us",
"ngrok.app",
"ngrok-free.app",
"ngrok.dev",
@@ -8453,206 +9028,207 @@ var rules = [...]string{
"sa.ngrok.io",
"us.ngrok.io",
"ngrok.pizza",
+ "ngrok.pro",
+ "torun.pl",
"nh-serv.co.uk",
- "nfshost.com",
- "*.developer.app",
- "noop.app",
- "*.northflank.app",
- "*.build.run",
- "*.code.run",
- "*.database.run",
- "*.migration.run",
- "noticeable.news",
- "dnsking.ch",
- "mypi.co",
- "n4t.co",
- "001www.com",
- "ddnslive.com",
- "myiphost.com",
- "forumz.info",
- "16-b.it",
- "32-b.it",
- "64-b.it",
- "soundcast.me",
- "tcp4.me",
- "dnsup.net",
- "hicam.net",
- "now-dns.net",
- "ownip.net",
- "vpndns.net",
- "dynserv.org",
- "now-dns.org",
- "x443.pw",
- "now-dns.top",
- "ntdll.top",
- "freeddns.us",
- "crafting.xyz",
- "zapto.xyz",
- "nsupdate.info",
- "nerdpol.ovh",
+ "nimsite.uk",
+ "mmafan.biz",
+ "myftp.biz",
+ "no-ip.biz",
+ "no-ip.ca",
+ "fantasyleague.cc",
+ "gotdns.ch",
+ "3utilities.com",
"blogsyte.com",
- "brasilia.me",
- "cable-modem.org",
"ciscofreak.com",
- "collegefan.org",
- "couchpotatofries.org",
"damnserver.com",
- "ddns.me",
+ "ddnsking.com",
"ditchyourip.com",
- "dnsfor.me",
"dnsiskinky.com",
- "dvrcam.info",
"dynns.com",
- "eating-organic.net",
- "fantasyleague.cc",
"geekgalaxy.com",
- "golffan.us",
"health-carereform.com",
"homesecuritymac.com",
"homesecuritypc.com",
- "hopto.me",
- "ilovecollege.info",
- "loginto.me",
- "mlbfan.org",
- "mmafan.biz",
"myactivedirectory.com",
- "mydissent.net",
- "myeffect.net",
- "mymediapc.net",
- "mypsx.net",
"mysecuritycamera.com",
- "mysecuritycamera.net",
- "mysecuritycamera.org",
+ "myvnc.com",
"net-freaks.com",
- "nflfan.org",
- "nhlfan.net",
- "no-ip.ca",
- "no-ip.co.uk",
- "no-ip.net",
- "noip.us",
"onthewifi.com",
- "pgafan.net",
"point2this.com",
- "pointto.us",
- "privatizehealthinsurance.net",
"quicksytes.com",
- "read-books.org",
"securitytactics.com",
+ "servebeer.com",
+ "servecounterstrike.com",
"serveexchange.com",
+ "serveftp.com",
+ "servegame.com",
+ "servehalflife.com",
+ "servehttp.com",
"servehumour.com",
+ "serveirc.com",
+ "servemp3.com",
"servep2p.com",
+ "servepics.com",
+ "servequake.com",
"servesarcasm.com",
"stufftoread.com",
- "ufcfan.org",
"unusualperson.com",
"workisboring.com",
- "3utilities.com",
- "bounceme.net",
- "ddns.net",
- "ddnsking.com",
- "gotdns.ch",
- "hopto.org",
- "myftp.biz",
- "myftp.org",
- "myvnc.com",
- "no-ip.biz",
+ "dvrcam.info",
+ "ilovecollege.info",
"no-ip.info",
- "no-ip.org",
+ "brasilia.me",
+ "ddns.me",
+ "dnsfor.me",
+ "hopto.me",
+ "loginto.me",
"noip.me",
+ "webhop.me",
+ "bounceme.net",
+ "ddns.net",
+ "eating-organic.net",
+ "mydissent.net",
+ "myeffect.net",
+ "mymediapc.net",
+ "mypsx.net",
+ "mysecuritycamera.net",
+ "nhlfan.net",
+ "no-ip.net",
+ "pgafan.net",
+ "privatizehealthinsurance.net",
"redirectme.net",
- "servebeer.com",
"serveblog.net",
- "servecounterstrike.com",
- "serveftp.com",
- "servegame.com",
- "servehalflife.com",
- "servehttp.com",
- "serveirc.com",
"serveminecraft.net",
- "servemp3.com",
- "servepics.com",
- "servequake.com",
"sytes.net",
- "webhop.me",
+ "cable-modem.org",
+ "collegefan.org",
+ "couchpotatofries.org",
+ "hopto.org",
+ "mlbfan.org",
+ "myftp.org",
+ "mysecuritycamera.org",
+ "nflfan.org",
+ "no-ip.org",
+ "read-books.org",
+ "ufcfan.org",
"zapto.org",
+ "no-ip.co.uk",
+ "golffan.us",
+ "noip.us",
+ "pointto.us",
"stage.nodeart.io",
- "pcloud.host",
+ "*.developer.app",
+ "noop.app",
+ "*.northflank.app",
+ "*.build.run",
+ "*.code.run",
+ "*.database.run",
+ "*.migration.run",
+ "noticeable.news",
+ "notion.site",
+ "dnsking.ch",
+ "mypi.co",
+ "myiphost.com",
+ "forumz.info",
+ "soundcast.me",
+ "tcp4.me",
+ "dnsup.net",
+ "hicam.net",
+ "now-dns.net",
+ "ownip.net",
+ "vpndns.net",
+ "dynserv.org",
+ "now-dns.org",
+ "x443.pw",
+ "ntdll.top",
+ "freeddns.us",
+ "nsupdate.info",
+ "nerdpol.ovh",
"nyc.mn",
+ "prvcy.page",
+ "obl.ong",
+ "observablehq.cloud",
"static.observableusercontent.com",
- "cya.gg",
"omg.lol",
"cloudycluster.net",
"omniwe.site",
- "123hjemmeside.dk",
- "123hjemmeside.no",
- "123homepage.it",
- "123kotisivu.fi",
- "123minsida.se",
- "123miweb.es",
- "123paginaweb.pt",
- "123sait.ru",
- "123siteweb.fr",
"123webseite.at",
- "123webseite.de",
"123website.be",
+ "simplesite.com.br",
"123website.ch",
+ "simplesite.com",
+ "123webseite.de",
+ "123hjemmeside.dk",
+ "123miweb.es",
+ "123kotisivu.fi",
+ "123siteweb.fr",
+ "simplesite.gr",
+ "123homepage.it",
"123website.lu",
"123website.nl",
+ "123hjemmeside.no",
"service.one",
- "simplesite.com",
- "simplesite.com.br",
- "simplesite.gr",
"simplesite.pl",
- "nid.io",
+ "123paginaweb.pt",
+ "123minsida.se",
+ "is-a-fullstack.dev",
+ "is-cool.dev",
+ "is-not-a.dev",
+ "localplayer.dev",
+ "is-local.org",
"opensocial.site",
"opencraft.hosting",
+ "16-b.it",
+ "32-b.it",
+ "64-b.it",
"orsites.com",
"operaunite.com",
+ "*.customer-oci.com",
+ "*.oci.customer-oci.com",
+ "*.ocp.customer-oci.com",
+ "*.ocs.customer-oci.com",
+ "*.oraclecloudapps.com",
+ "*.oraclegovcloudapps.com",
+ "*.oraclegovcloudapps.uk",
"tech.orange",
+ "can.re",
"authgear-staging.com",
"authgearapps.com",
"skygearapp.com",
"outsystemscloud.com",
- "*.webpaas.ovh.net",
"*.hosting.ovh.net",
+ "*.webpaas.ovh.net",
"ownprovider.com",
"own.pm",
"*.owo.codes",
"ox.rs",
"oy.lc",
"pgfog.com",
- "pagefrontapp.com",
"pagexl.com",
- "*.paywhirl.com",
- "bar0.net",
- "bar1.net",
- "bar2.net",
- "rdv.to",
- "art.pl",
- "gliwice.pl",
- "krakow.pl",
- "poznan.pl",
- "wroc.pl",
- "zakopane.pl",
- "pantheonsite.io",
"gotpantheon.com",
+ "pantheonsite.io",
+ "*.paywhirl.com",
+ "*.xmit.co",
+ "xmit.dev",
+ "madethis.site",
+ "srv.us",
+ "gh.srv.us",
+ "gl.srv.us",
+ "lk3.ru",
"mypep.link",
"perspecta.cloud",
- "lk3.ru",
"on-web.fr",
- "bc.platform.sh",
+ "*.upsun.app",
+ "upsunapp.com",
"ent.platform.sh",
"eu.platform.sh",
"us.platform.sh",
"*.platformsh.site",
"*.tst.site",
- "platter-app.com",
"platter-app.dev",
"platterp.us",
- "pdns.page",
- "plesk.page",
- "pleskns.com",
- "dyn53.io",
+ "pley.games",
"onporter.run",
"co.bn",
"postman-echo.com",
@@ -8662,55 +9238,102 @@ var rules = [...]string{
"prequalifyme.today",
"xen.prgmr.com",
"priv.at",
- "prvcy.page",
- "*.dweb.link",
+ "c01.kr",
+ "eliv-dns.kr",
"protonet.io",
+ "sub.psl.hrsn.dev",
+ "*.wc.psl.hrsn.dev",
+ "!ignored.wc.psl.hrsn.dev",
+ "*.sub.wc.psl.hrsn.dev",
+ "!ignored.sub.wc.psl.hrsn.dev",
"chirurgiens-dentistes-en-france.fr",
"byen.site",
"pubtls.org",
"pythonanywhere.com",
"eu.pythonanywhere.com",
- "qoto.io",
- "qualifioapp.com",
- "ladesk.com",
- "qbuser.com",
- "cloudsite.builders",
- "instances.spawn.cc",
- "instantcloud.cn",
- "ras.ru",
"qa2.com",
"qcx.io",
"*.sys.qcx.io",
- "dev-myqnapcloud.com",
+ "myqnapcloud.cn",
"alpha-myqnapcloud.com",
+ "dev-myqnapcloud.com",
+ "mycloudnas.com",
+ "mynascloud.com",
"myqnapcloud.com",
+ "qoto.io",
+ "qualifioapp.com",
+ "ladesk.com",
+ "qbuser.com",
"*.quipelements.com",
"vapor.cloud",
"vaporcloud.io",
"rackmaze.com",
"rackmaze.net",
- "g.vbrplsbx.io",
- "*.on-k3s.io",
+ "cloudsite.builders",
+ "myradweb.net",
+ "servername.us",
+ "web.in",
+ "in.net",
+ "myrdbx.io",
+ "site.rb-hosting.io",
"*.on-rancher.cloud",
+ "*.on-k3s.io",
"*.on-rio.io",
+ "ravpage.co.il",
+ "readthedocs-hosted.com",
"readthedocs.io",
"rhcloud.com",
- "app.render.com",
+ "instances.spawn.cc",
"onrender.com",
+ "app.render.com",
+ "replit.app",
+ "id.replit.app",
"firewalledreplit.co",
"id.firewalledreplit.co",
"repl.co",
"id.repl.co",
+ "replit.dev",
+ "archer.replit.dev",
+ "bones.replit.dev",
+ "canary.replit.dev",
+ "global.replit.dev",
+ "hacker.replit.dev",
+ "id.replit.dev",
+ "janeway.replit.dev",
+ "kim.replit.dev",
+ "kira.replit.dev",
+ "kirk.replit.dev",
+ "odo.replit.dev",
+ "paris.replit.dev",
+ "picard.replit.dev",
+ "pike.replit.dev",
+ "prerelease.replit.dev",
+ "reed.replit.dev",
+ "riker.replit.dev",
+ "sisko.replit.dev",
+ "spock.replit.dev",
+ "staging.replit.dev",
+ "sulu.replit.dev",
+ "tarpit.replit.dev",
+ "teams.replit.dev",
+ "tucker.replit.dev",
+ "wesley.replit.dev",
+ "worf.replit.dev",
"repl.run",
"resindevice.io",
"devices.resinstaging.io",
"hzc.io",
- "wellbeingzone.eu",
- "wellbeingzone.co.uk",
"adimo.co.uk",
"itcouldbewor.se",
+ "aus.basketball",
+ "nz.basketball",
+ "subsc-pay.com",
+ "subsc-pay.net",
"git-pages.rit.edu",
"rocky.page",
+ "rub.de",
+ "ruhr-uni-bochum.de",
+ "io.noc.ruhr-uni-bochum.de",
"xn--90amc.xn--p1acf",
"xn--j1aef.xn--p1acf",
"xn--j1ael8b.xn--p1acf",
@@ -8721,6 +9344,8 @@ var rules = [...]string{
"xn--h1aliz.xn--p1acf",
"xn--90a1af.xn--p1acf",
"xn--41a.xn--p1acf",
+ "ras.ru",
+ "nyat.app",
"180r.com",
"dojin.com",
"sakuratan.com",
@@ -8771,12 +9396,22 @@ var rules = [...]string{
"*.builder.code.com",
"*.dev-builder.code.com",
"*.stg-builder.code.com",
+ "*.001.test.code-builder-stg.platform.salesforce.com",
+ "*.d.crm.dev",
+ "*.w.crm.dev",
+ "*.wa.crm.dev",
+ "*.wb.crm.dev",
+ "*.wc.crm.dev",
+ "*.wd.crm.dev",
+ "*.we.crm.dev",
+ "*.wf.crm.dev",
"sandcats.io",
- "logoip.de",
"logoip.com",
+ "logoip.de",
"fr-par-1.baremetal.scw.cloud",
"fr-par-2.baremetal.scw.cloud",
"nl-ams-1.baremetal.scw.cloud",
+ "cockpit.fr-par.scw.cloud",
"fnc.fr-par.scw.cloud",
"functions.fnc.fr-par.scw.cloud",
"k8s.fr-par.scw.cloud",
@@ -8787,11 +9422,13 @@ var rules = [...]string{
"priv.instances.scw.cloud",
"pub.instances.scw.cloud",
"k8s.scw.cloud",
+ "cockpit.nl-ams.scw.cloud",
"k8s.nl-ams.scw.cloud",
"nodes.k8s.nl-ams.scw.cloud",
"s3.nl-ams.scw.cloud",
"s3-website.nl-ams.scw.cloud",
"whm.nl-ams.scw.cloud",
+ "cockpit.pl-waw.scw.cloud",
"k8s.pl-waw.scw.cloud",
"nodes.k8s.pl-waw.scw.cloud",
"s3.pl-waw.scw.cloud",
@@ -8803,6 +9440,7 @@ var rules = [...]string{
"gov.scot",
"service.gov.scot",
"scrysec.com",
+ "client.scrypted.io",
"firewall-gateway.com",
"firewall-gateway.de",
"my-gateway.de",
@@ -8815,18 +9453,18 @@ var rules = [...]string{
"spdns.org",
"seidat.net",
"sellfy.store",
- "senseering.net",
"minisite.ms",
- "magnet.page",
+ "senseering.net",
+ "servebolt.cloud",
"biz.ua",
"co.ua",
"pp.ua",
- "shiftcrypto.dev",
- "shiftcrypto.io",
- "shiftedit.io",
+ "as.sh.cn",
+ "sheezy.games",
"myshopblocks.com",
"myshopify.com",
"shopitsite.com",
+ "shopware.shop",
"shopware.store",
"mo-siemens.io",
"1kapp.com",
@@ -8835,32 +9473,29 @@ var rules = [...]string{
"sinaapp.com",
"vipsinaapp.com",
"siteleaf.net",
- "bounty-full.com",
- "alpha.bounty-full.com",
- "beta.bounty-full.com",
"small-web.org",
+ "aeroport.fr",
+ "avocat.fr",
+ "chambagri.fr",
+ "chirurgiens-dentistes.fr",
+ "experts-comptables.fr",
+ "medecin.fr",
+ "notaires.fr",
+ "pharmacien.fr",
+ "port.fr",
+ "veterinaire.fr",
"vp4.me",
- "snowflake.app",
- "privatelink.snowflake.app",
+ "*.snowflake.app",
+ "*.privatelink.snowflake.app",
"streamlit.app",
"streamlitapp.com",
"try-snowplow.com",
- "srht.site",
- "stackhero-network.com",
- "musician.io",
- "novecore.site",
- "static.land",
- "dev.static.land",
- "sites.static.land",
- "storebase.store",
- "vps-host.net",
- "atl.jelastic.vps-host.net",
- "njs.jelastic.vps-host.net",
- "ric.jelastic.vps-host.net",
+ "mafelo.net",
"playstation-cloud.com",
+ "srht.site",
"apps.lair.io",
"*.stolos.io",
- "spacekit.io",
+ "ind.mom",
"customer.speedpartner.de",
"myspreadshop.at",
"myspreadshop.com.au",
@@ -8881,19 +9516,47 @@ var rules = [...]string{
"myspreadshop.pl",
"myspreadshop.se",
"myspreadshop.co.uk",
+ "w-corp-staticblitz.com",
+ "w-credentialless-staticblitz.com",
+ "w-staticblitz.com",
+ "stackhero-network.com",
+ "runs.onstackit.cloud",
+ "stackit.gg",
+ "stackit.rocks",
+ "stackit.run",
+ "stackit.zone",
+ "musician.io",
+ "novecore.site",
"api.stdlib.com",
+ "feedback.ac",
+ "forms.ac",
+ "assessments.cx",
+ "calculators.cx",
+ "funnels.cx",
+ "paynow.cx",
+ "quizzes.cx",
+ "researched.cx",
+ "tests.cx",
+ "surveys.so",
+ "storebase.store",
"storipress.app",
"storj.farm",
- "utwente.io",
+ "strapiapp.com",
+ "media.strapiapp.com",
+ "vps-host.net",
+ "atl.jelastic.vps-host.net",
+ "njs.jelastic.vps-host.net",
+ "ric.jelastic.vps-host.net",
+ "streak-link.com",
+ "streaklinks.com",
+ "streakusercontent.com",
"soc.srcf.net",
"user.srcf.net",
+ "utwente.io",
"temp-dns.com",
"supabase.co",
"supabase.in",
"supabase.net",
- "su.paba.se",
- "*.s5y.io",
- "*.sensiosite.cloud",
"syncloud.it",
"dscloud.biz",
"direct.quickconnect.cn",
@@ -8909,19 +9572,22 @@ var rules = [...]string{
"familyds.net",
"dsmynas.org",
"familyds.org",
- "vpnplus.to",
"direct.quickconnect.to",
- "tabitorder.co.il",
- "mytabit.co.il",
+ "vpnplus.to",
"mytabit.com",
+ "mytabit.co.il",
+ "tabitorder.co.il",
"taifun-dns.de",
- "beta.tailscale.net",
"ts.net",
+ "*.c.ts.net",
"gda.pl",
"gdansk.pl",
"gdynia.pl",
"med.pl",
"sopot.pl",
+ "taveusercontent.com",
+ "p.tawk.email",
+ "p.tawkto.email",
"site.tb-hosting.com",
"edugit.io",
"s3.teckids.org",
@@ -8933,11 +9599,11 @@ var rules = [...]string{
"reservd.com",
"thingdustdata.com",
"cust.dev.thingdust.io",
+ "reservd.dev.thingdust.io",
"cust.disrec.thingdust.io",
+ "reservd.disrec.thingdust.io",
"cust.prod.thingdust.io",
"cust.testing.thingdust.io",
- "reservd.dev.thingdust.io",
- "reservd.disrec.thingdust.io",
"reservd.testing.thingdust.io",
"tickets.io",
"arvo.network",
@@ -8945,7 +9611,6 @@ var rules = [...]string{
"tlon.network",
"torproject.net",
"pages.torproject.net",
- "bloxcms.com",
"townnews-staging.com",
"12hp.at",
"2ix.at",
@@ -8968,12 +9633,10 @@ var rules = [...]string{
"lima.zone",
"*.transurl.be",
"*.transurl.eu",
- "*.transurl.nl",
"site.transip.me",
+ "*.transurl.nl",
"tuxfamily.org",
"dd-dns.de",
- "diskstation.eu",
- "diskstation.org",
"dray-dns.de",
"draydns.de",
"dyn-vpn.de",
@@ -8984,90 +9647,74 @@ var rules = [...]string{
"syno-ds.de",
"synology-diskstation.de",
"synology-ds.de",
+ "diskstation.eu",
+ "diskstation.org",
"typedream.app",
"pro.typeform.com",
- "uber.space",
"*.uberspace.de",
+ "uber.space",
"hk.com",
- "hk.org",
- "ltd.hk",
"inc.hk",
+ "ltd.hk",
+ "hk.org",
"it.com",
+ "unison-services.cloud",
+ "virtual-user.de",
+ "virtualuser.de",
+ "obj.ag",
"name.pm",
"sch.tf",
"biz.wf",
"sch.wf",
"org.yt",
- "virtualuser.de",
- "virtual-user.de",
- "upli.io",
+ "rs.ba",
+ "bielsko.pl",
"urown.cloud",
"dnsupdate.info",
- "lib.de.us",
- "2038.io",
+ "us.org",
+ "v.ua",
+ "express.val.run",
+ "web.val.run",
"vercel.app",
+ "v0.build",
"vercel.dev",
+ "vusercontent.net",
"now.sh",
- "router.management",
+ "2038.io",
"v-info.info",
+ "deus-canvas.com",
"voorloper.cloud",
- "neko.am",
- "nyaa.am",
- "be.ax",
- "cat.ax",
- "es.ax",
- "eu.ax",
- "gg.ax",
- "mc.ax",
- "us.ax",
- "xy.ax",
- "nl.ci",
- "xx.gl",
- "app.gp",
- "blog.gt",
- "de.gt",
- "to.gt",
- "be.gy",
- "cc.hn",
- "blog.kg",
- "io.kg",
- "jp.kg",
- "tv.kg",
- "uk.kg",
- "us.kg",
- "de.ls",
- "at.md",
- "de.md",
- "jp.md",
- "to.md",
- "indie.porn",
- "vxl.sh",
- "ch.tc",
- "me.tc",
- "we.tc",
- "nyan.to",
- "at.vg",
- "blog.vu",
- "dev.vu",
- "me.vu",
- "v.ua",
"*.vultrobjects.com",
"wafflecell.com",
+ "webflow.io",
+ "webflowtest.io",
"*.webhare.dev",
- "reserve-online.net",
- "reserve-online.com",
"bookonline.app",
"hotelwithflight.com",
- "wedeploy.io",
- "wedeploy.me",
- "wedeploy.sh",
+ "reserve-online.com",
+ "reserve-online.net",
+ "cprapid.com",
+ "pleskns.com",
+ "wp2.host",
+ "pdns.page",
+ "plesk.page",
+ "cpanel.site",
+ "wpsquared.site",
+ "*.wadl.top",
"remotewd.com",
+ "box.ca",
"pages.wiardweb.com",
- "wmflabs.org",
"toolforge.org",
"wmcloud.org",
+ "wmflabs.org",
+ "wdh.app",
"panel.gg",
"daemon.panel.gg",
+ "wixsite.com",
+ "wixstudio.com",
+ "editorx.io",
+ "wixstudio.io",
+ "wix.run",
"messwithdns.com",
"woltlab-demo.com",
"myforum.community",
@@ -9080,8 +9727,6 @@ var rules = [...]string{
"weeklylottery.org.uk",
"wpenginepowered.com",
"js.wpenginepowered.com",
- "wixsite.com",
- "editorx.io",
"half.host",
"xnbay.com",
"u2.xnbay.com",
@@ -9094,22 +9739,19 @@ var rules = [...]string{
"website.yandexcloud.net",
"official.academy",
"yolasite.com",
- "ybo.faith",
- "yombo.me",
- "homelink.one",
- "ybo.party",
- "ybo.review",
- "ybo.science",
- "ybo.trade",
"ynh.fr",
"nohost.me",
"noho.st",
"za.net",
"za.org",
+ "zap.cloud",
+ "zeabur.app",
+ "*.zerops.app",
"bss.design",
"basicserver.io",
"virtualserver.io",
"enterprisecloud.nu",
+ "zone.id",
}
var nodeLabels = [...]string{
@@ -9199,7 +9841,6 @@ var nodeLabels = [...]string{
"author",
"auto",
"autos",
- "avianca",
"aw",
"aws",
"ax",
@@ -9210,7 +9851,6 @@ var nodeLabels = [...]string{
"baby",
"baidu",
"banamex",
- "bananarepublic",
"band",
"bank",
"bar",
@@ -9327,7 +9967,6 @@ var nodeLabels = [...]string{
"cba",
"cbn",
"cbre",
- "cbs",
"cc",
"cd",
"center",
@@ -9356,7 +9995,6 @@ var nodeLabels = [...]string{
"citi",
"citic",
"city",
- "cityeats",
"ck",
"cl",
"claims",
@@ -9377,7 +10015,6 @@ var nodeLabels = [...]string{
"college",
"cologne",
"com",
- "comcast",
"commbank",
"community",
"company",
@@ -9416,7 +10053,6 @@ var nodeLabels = [...]string{
"cymru",
"cyou",
"cz",
- "dabur",
"dad",
"dance",
"data",
@@ -9495,7 +10131,6 @@ var nodeLabels = [...]string{
"esq",
"estate",
"et",
- "etisalat",
"eu",
"eurovision",
"eus",
@@ -9558,7 +10193,6 @@ var nodeLabels = [...]string{
"fresenius",
"frl",
"frogans",
- "frontdoor",
"frontier",
"ftr",
"fujitsu",
@@ -9630,7 +10264,6 @@ var nodeLabels = [...]string{
"gs",
"gt",
"gu",
- "guardian",
"gucci",
"guge",
"guide",
@@ -9750,7 +10383,6 @@ var nodeLabels = [...]string{
"kddi",
"ke",
"kerryhotels",
- "kerrylogistics",
"kerryproperties",
"kfh",
"kg",
@@ -9759,7 +10391,6 @@ var nodeLabels = [...]string{
"kia",
"kids",
"kim",
- "kinder",
"kindle",
"kitchen",
"kiwi",
@@ -9815,7 +10446,6 @@ var nodeLabels = [...]string{
"limo",
"lincoln",
"link",
- "lipsy",
"live",
"living",
"lk",
@@ -9871,6 +10501,7 @@ var nodeLabels = [...]string{
"memorial",
"men",
"menu",
+ "merck",
"merckmsd",
"mg",
"mh",
@@ -9925,7 +10556,6 @@ var nodeLabels = [...]string{
"nab",
"nagoya",
"name",
- "natura",
"navy",
"nba",
"nc",
@@ -9956,7 +10586,6 @@ var nodeLabels = [...]string{
"nl",
"no",
"nokia",
- "northwesternmutual",
"norton",
"now",
"nowruz",
@@ -9975,7 +10604,6 @@ var nodeLabels = [...]string{
"okinawa",
"olayan",
"olayangroup",
- "oldnavy",
"ollo",
"om",
"omega",
@@ -10103,7 +10731,6 @@ var nodeLabels = [...]string{
"rio",
"rip",
"ro",
- "rocher",
"rocks",
"rodeo",
"rogers",
@@ -10138,7 +10765,6 @@ var nodeLabels = [...]string{
"sbi",
"sbs",
"sc",
- "sca",
"scb",
"schaeffler",
"schmidt",
@@ -10167,7 +10793,6 @@ var nodeLabels = [...]string{
"sh",
"shangrila",
"sharp",
- "shaw",
"shell",
"shia",
"shiksha",
@@ -10176,7 +10801,6 @@ var nodeLabels = [...]string{
"shopping",
"shouji",
"show",
- "showtime",
"si",
"silk",
"sina",
@@ -10357,7 +10981,6 @@ var nodeLabels = [...]string{
"vlaanderen",
"vn",
"vodka",
- "volkswagen",
"volvo",
"vote",
"voting",
@@ -10376,6 +10999,7 @@ var nodeLabels = [...]string{
"webcam",
"weber",
"website",
+ "wed",
"wedding",
"weibo",
"weir",
@@ -10400,7 +11024,6 @@ var nodeLabels = [...]string{
"wtf",
"xbox",
"xerox",
- "xfinity",
"xihuan",
"xin",
"xn--11b4c3d",
@@ -10493,7 +11116,6 @@ var nodeLabels = [...]string{
"xn--mgba3a4f16a",
"xn--mgba3a4fra",
"xn--mgba7c0bbn0a",
- "xn--mgbaakc7dvf",
"xn--mgbaam7a8h",
"xn--mgbab2bd",
"xn--mgbah1a3hjkrd",
@@ -10590,14 +11212,14 @@ var nodeLabels = [...]string{
"com",
"drr",
"edu",
+ "feedback",
+ "forms",
"gov",
"mil",
"net",
"org",
"official",
- "nom",
"ac",
- "blogspot",
"co",
"gov",
"mil",
@@ -10617,7 +11239,6 @@ var nodeLabels = [...]string{
"airport",
"airtraffic",
"ambulance",
- "amusement",
"association",
"author",
"ballooning",
@@ -10648,6 +11269,7 @@ var nodeLabels = [...]string{
"express",
"federation",
"flight",
+ "freight",
"fuel",
"gliding",
"government",
@@ -10662,6 +11284,7 @@ var nodeLabels = [...]string{
"logistics",
"magazine",
"maintenance",
+ "marketplace",
"media",
"microlight",
"modelling",
@@ -10684,6 +11307,7 @@ var nodeLabels = [...]string{
"skydiving",
"software",
"student",
+ "taxi",
"trader",
"trading",
"trainer",
@@ -10699,66 +11323,87 @@ var nodeLabels = [...]string{
"com",
"net",
"nom",
+ "obj",
"org",
"com",
+ "framer",
"net",
"off",
"org",
"uwu",
- "blogspot",
"com",
"edu",
"gov",
"mil",
"net",
"org",
- "blogspot",
"co",
"com",
"commune",
- "neko",
"net",
- "nyaa",
"org",
"radio",
"co",
"ed",
+ "edu",
+ "gov",
"gv",
"it",
"og",
+ "org",
"pb",
+ "adaptable",
+ "aiven",
"beget",
"bookonline",
+ "botdash",
+ "brave",
"clerk",
"clerkstage",
+ "csb",
"deta",
"developer",
"easypanel",
"edgecompute",
"encr",
- "fireweb",
+ "evervault",
+ "expo",
+ "flutterflow",
"framer",
"hasura",
+ "hosted",
"loginline",
+ "lovable",
+ "medusajs",
"messerli",
+ "netfy",
"netlify",
"ngrok",
"ngrok-free",
"noop",
"northflank",
+ "nyat",
+ "on-fleek",
"ondigitalocean",
- "onflashdrive",
- "platform0",
+ "replit",
"run",
"snowflake",
"storipress",
"streamlit",
"telebit",
"typedream",
+ "upsun",
"vercel",
+ "wdh",
"web",
"wnext",
- "a",
+ "zeabur",
+ "zerops",
+ "s",
+ "preview",
+ "relay",
+ "staging",
+ "id",
"privatelink",
"bet",
"com",
@@ -10774,8 +11419,8 @@ var nodeLabels = [...]string{
"org",
"senasa",
"tur",
- "blogspot",
"e164",
+ "home",
"in-addr",
"ip6",
"iris",
@@ -10783,6 +11428,8 @@ var nodeLabels = [...]string{
"urn",
"gov",
"cloudns",
+ "daemon",
+ "dix",
"123webseite",
"12hp",
"2ix",
@@ -10802,7 +11449,6 @@ var nodeLabels = [...]string{
"ortsinfo",
"priv",
"sth",
- "blogspot",
"wien",
"ex",
"in",
@@ -10815,7 +11461,6 @@ var nodeLabels = [...]string{
"edu",
"gov",
"id",
- "info",
"net",
"nsw",
"nt",
@@ -10826,7 +11471,6 @@ var nodeLabels = [...]string{
"tas",
"vic",
"wa",
- "blogspot",
"cloudlets",
"myspreadshop",
"mel",
@@ -10846,15 +11490,143 @@ var nodeLabels = [...]string{
"vic",
"wa",
"com",
- "be",
- "cat",
- "es",
- "eu",
- "gg",
- "mc",
- "us",
- "xy",
+ "on",
+ "repost",
+ "sagemaker",
+ "ap-northeast-1",
+ "ap-southeast-1",
+ "ap-southeast-2",
+ "eu-central-1",
+ "eu-north-1",
+ "eu-west-1",
+ "us-east-1",
+ "us-east-2",
+ "us-west-2",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "transfer-webapp",
+ "private",
+ "af-south-1",
+ "ap-east-1",
+ "ap-northeast-1",
+ "ap-northeast-2",
+ "ap-northeast-3",
+ "ap-south-1",
+ "ap-south-2",
+ "ap-southeast-1",
+ "ap-southeast-2",
+ "ap-southeast-3",
+ "ap-southeast-4",
+ "ca-central-1",
+ "ca-west-1",
+ "eu-central-1",
+ "eu-central-2",
+ "eu-north-1",
+ "eu-south-1",
+ "eu-south-2",
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3",
+ "experiments",
+ "il-central-1",
+ "me-central-1",
+ "me-south-1",
+ "sa-east-1",
+ "us-east-1",
+ "us-east-2",
+ "us-gov-east-1",
+ "us-gov-west-1",
+ "us-west-1",
+ "us-west-2",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "notebook",
+ "labeling",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "labeling",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "notebook",
+ "notebook-fips",
+ "labeling",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
+ "labeling",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "labeling",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "studio-fips",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "studio-fips",
+ "notebook",
+ "notebook-fips",
+ "studio",
+ "labeling",
+ "notebook",
+ "notebook-fips",
+ "studio",
"biz",
+ "co",
"com",
"edu",
"gov",
@@ -10866,7 +11638,6 @@ var nodeLabels = [...]string{
"org",
"pp",
"pro",
- "blogspot",
"com",
"edu",
"gov",
@@ -10888,7 +11659,7 @@ var nodeLabels = [...]string{
"tv",
"123website",
"ac",
- "blogspot",
+ "cloudns",
"interhostsolutions",
"kuleuven",
"myspreadshop",
@@ -10910,7 +11681,6 @@ var nodeLabels = [...]string{
"a",
"b",
"barsy",
- "blogspot",
"c",
"d",
"e",
@@ -10946,6 +11716,7 @@ var nodeLabels = [...]string{
"or",
"org",
"activetrail",
+ "cloud-ip",
"cloudns",
"dscloud",
"dyndns",
@@ -10965,7 +11736,6 @@ var nodeLabels = [...]string{
"architectes",
"assur",
"avocats",
- "blogspot",
"co",
"com",
"eco",
@@ -11049,6 +11819,7 @@ var nodeLabels = [...]string{
"b",
"barueri",
"belem",
+ "bet",
"bhz",
"bib",
"bio",
@@ -11108,6 +11879,7 @@ var nodeLabels = [...]string{
"jor",
"jus",
"leg",
+ "leilao",
"lel",
"log",
"londrina",
@@ -11174,7 +11946,6 @@ var nodeLabels = [...]string{
"vlog",
"wiki",
"zlg",
- "blogspot",
"simplesite",
"ac",
"al",
@@ -11241,21 +12012,25 @@ var nodeLabels = [...]string{
"gov",
"net",
"org",
+ "v0",
"cloudsite",
"co",
+ "ac",
"co",
+ "gov",
+ "net",
"org",
"com",
"gov",
"mediatech",
"mil",
- "mycloud",
"of",
- "blogspot",
+ "co",
"com",
"edu",
"gov",
"gsj",
+ "mydns",
"net",
"org",
"za",
@@ -11263,7 +12038,7 @@ var nodeLabels = [...]string{
"awdev",
"barsy",
"bc",
- "blogspot",
+ "box",
"co",
"gc",
"mb",
@@ -11280,8 +12055,11 @@ var nodeLabels = [...]string{
"qc",
"sk",
"yk",
+ "emf",
+ "at",
"nabu",
"ui",
+ "cleverapps",
"cloudns",
"csx",
"fantasyleague",
@@ -11293,12 +12071,12 @@ var nodeLabels = [...]string{
"twmail",
"instances",
"gov",
- "blogspot",
"123website",
"12hp",
"2ix",
"4lima",
- "blogspot",
+ "cloudns",
+ "cloudscale",
"dnsking",
"firenet",
"flow",
@@ -11307,6 +12085,11 @@ var nodeLabels = [...]string{
"linkyard-cloud",
"myspreadshop",
"square7",
+ "cust",
+ "lpg",
+ "rma",
+ "objects",
+ "objects",
"svc",
"ae",
"appengine",
@@ -11317,25 +12100,20 @@ var nodeLabels = [...]string{
"com",
"ed",
"edu",
- "fin",
"go",
"gouv",
"int",
- "md",
"net",
- "nl",
"or",
"org",
- "presse",
"xn--aroport-bya",
"www",
- "blogspot",
+ "cloudns",
"co",
"gob",
"gov",
"mil",
"axarnet",
- "banzai",
"diadem",
"elementor",
"encoway",
@@ -11345,22 +12123,28 @@ var nodeLabels = [...]string{
"jotelulu",
"keliweb",
"kuleuven",
+ "laravel",
"linkyard",
"magentosite",
+ "matlab",
+ "observablehq",
"on-rancher",
+ "onstackit",
"oxa",
"perspecta",
"primetel",
"ravendb",
"reclaim",
"scw",
- "sensiosite",
+ "servebolt",
"statics",
"trafficplex",
"trendhosting",
+ "unison-services",
"urown",
"vapor",
"voorloper",
+ "zap",
"es-1",
"eu",
"vip",
@@ -11369,6 +12153,7 @@ var nodeLabels = [...]string{
"eur",
"it1",
"cs",
+ "runs",
"tn",
"uk",
"uk",
@@ -11386,6 +12171,7 @@ var nodeLabels = [...]string{
"fr-par-1",
"fr-par-2",
"nl-ams-1",
+ "cockpit",
"fnc",
"k8s",
"s3",
@@ -11395,11 +12181,13 @@ var nodeLabels = [...]string{
"nodes",
"priv",
"pub",
+ "cockpit",
"k8s",
"s3",
"s3-website",
"whm",
"nodes",
+ "cockpit",
"k8s",
"s3",
"s3-website",
@@ -11417,6 +12205,7 @@ var nodeLabels = [...]string{
"ah",
"bj",
"canva-apps",
+ "canvasite",
"com",
"cq",
"edu",
@@ -11433,13 +12222,13 @@ var nodeLabels = [...]string{
"hk",
"hl",
"hn",
- "instantcloud",
"jl",
"js",
"jx",
"ln",
"mil",
"mo",
+ "myqnapcloud",
"net",
"nm",
"nx",
@@ -11460,109 +12249,142 @@ var nodeLabels = [...]string{
"xz",
"yn",
"zj",
+ "my",
"amazonaws",
+ "sagemaker",
+ "airflow",
"cn-north-1",
+ "cn-northwest-1",
"compute",
"eb",
"elb",
+ "cn-north-1",
+ "cn-northwest-1",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-deprecated",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"s3",
+ "s3-accesspoint",
"cn-north-1",
"cn-northwest-1",
+ "cn-north-1",
+ "cn-northwest-1",
+ "notebook",
+ "studio",
+ "notebook",
+ "studio",
"direct",
- "arts",
+ "as",
"carrd",
"com",
"crd",
"edu",
"firewalledreplit",
- "firm",
"gov",
- "info",
- "int",
"leadpages",
"lpages",
"mil",
"mypi",
- "n4t",
"net",
"nom",
"org",
"otap",
- "rec",
"repl",
"supabase",
- "web",
- "blogspot",
+ "xmit",
"id",
"id",
"owo",
- "001www",
"0emm",
"180r",
"1kapp",
"3utilities",
"4u",
+ "a2hosted",
"adobeaemcloud",
"africa",
"airkitapps",
"airkitapps-au",
"aivencloud",
+ "aliases121",
+ "alibabacloudcs",
"alpha-myqnapcloud",
"amazonaws",
- "amscompute",
+ "amazoncognito",
+ "amplifyapp",
"appchizi",
"applinzi",
+ "apps-1and1",
"appspacehosted",
"appspaceusercontent",
"appspot",
- "ar",
+ "atmeta",
"authgear-staging",
"authgearapps",
+ "awsapprunner",
+ "awsapps",
"awsglobalaccelerator",
- "awsmppl",
"balena-devices",
"barsycenter",
"barsyonline",
- "betainabox",
"blogdns",
"blogspot",
"blogsyte",
- "bloxcms",
- "bounty-full",
"boutir",
"bplaced",
"br",
"builtwithdark",
"cafjs",
"canva-apps",
+ "cdn77-storage",
"cechire",
"cf-ipfs",
"ciscofreak",
- "cloudcontrolapp",
- "cloudcontrolled",
+ "clever-cloud",
"cloudflare-ipfs",
"cn",
"co",
"code",
"codespot",
+ "cprapid",
+ "cpserver",
"customer-oci",
"damnserver",
"datadetect",
"dattolocal",
"dattorelay",
"dattoweb",
- "ddns5",
"ddnsfree",
"ddnsgeek",
"ddnsking",
- "ddnslive",
"de",
+ "deus-canvas",
"dev-myqnapcloud",
- "devcdnaccesso",
+ "devinapps",
"digitaloceanspaces",
"discordsays",
"discordsez",
"ditchyourip",
+ "dnsabr",
"dnsalias",
"dnsdojo",
"dnsiskinky",
@@ -11574,6 +12396,7 @@ var nodeLabels = [...]string{
"drayddns",
"dreamhosters",
"dsmynas",
+ "durumis",
"dyn-o-saur",
"dynalias",
"dyndns-at-home",
@@ -11671,9 +12494,11 @@ var nodeLabels = [...]string{
"gotdns",
"gotpantheon",
"gr",
+ "grayjayleagues",
+ "hatenablog",
+ "hatenadiary",
"health-carereform",
"herokuapp",
- "herokussl",
"hk",
"hobby-site",
"homelinux",
@@ -11684,11 +12509,9 @@ var nodeLabels = [...]string{
"hostedpi",
"hosteur",
"hotelwithflight",
- "hu",
"iamallama",
"ik-server",
- "impertrix",
- "impertrixcdn",
+ "ip-ddns",
"is-a-anarchist",
"is-a-blogger",
"is-a-bookkeeper",
@@ -11753,19 +12576,18 @@ var nodeLabels = [...]string{
"joyent",
"jpn",
"kasserver",
- "kilatiron",
"kozow",
- "kr",
- "ktistory",
"ladesk",
"likes-pie",
"likescandy",
"linode",
"linodeobjects",
"linodeusercontent",
- "lmpm",
+ "live-website",
+ "localtonet",
"logoip",
"loseyourip",
+ "lovableproject",
"lpusercontent",
"massivegrid",
"mazeplay",
@@ -11773,12 +12595,17 @@ var nodeLabels = [...]string{
"meteorapp",
"mex",
"miniserver",
+ "modelscape",
+ "mwcloudnonprod",
"myactivedirectory",
"myasustor",
+ "mycloudnas",
"mydatto",
+ "mydbserver",
"mydobiss",
"mydrobo",
"myiphost",
+ "mynascloud",
"myqnapcloud",
"mysecuritycamera",
"myshopblocks",
@@ -11791,7 +12618,6 @@ var nodeLabels = [...]string{
"neat-url",
"net-freaks",
"nfshost",
- "no",
"nospamproxy",
"observableusercontent",
"on-aptible",
@@ -11800,39 +12626,41 @@ var nodeLabels = [...]string{
"onthewifi",
"ooguy",
"operaunite",
+ "oraclecloudapps",
+ "oraclegovcloudapps",
"orsites",
"outsystemscloud",
"ownprovider",
- "pagefrontapp",
"pagespeedmobilizer",
"pagexl",
"paywhirl",
"pgfog",
"pixolino",
- "platter-app",
"playstation-cloud",
"pleskns",
"point2this",
+ "polyspace",
"postman-echo",
"prgmr",
- "publishproxy",
"pythonanywhere",
"qa2",
"qbuser",
- "qc",
"qualifioapp",
"quicksytes",
"quipelements",
"rackmaze",
+ "readthedocs-hosted",
"remotewd",
"render",
"reservd",
"reserve-online",
"rhcloud",
+ "routingthecloud",
"ru",
"sa",
"sakuratan",
"sakuraweb",
+ "salesforce",
"saves-the-whales",
"scrysec",
"securitytactics",
@@ -11864,8 +12692,14 @@ var nodeLabels = [...]string{
"space-to-rent",
"stackhero-network",
"stdlib",
+ "strapiapp",
+ "streak-link",
+ "streaklinks",
+ "streakusercontent",
"streamlitapp",
"stufftoread",
+ "subsc-pay",
+ "taveusercontent",
"tb-hosting",
"teaches-yoga",
"temp-dns",
@@ -11878,16 +12712,21 @@ var nodeLabels = [...]string{
"typeform",
"uk",
"unusualperson",
+ "upsunapp",
"us",
- "uy",
"vipsinaapp",
"vultrobjects",
+ "w-corp-staticblitz",
+ "w-credentialless-staticblitz",
+ "w-staticblitz",
"wafaicloud",
"wafflecell",
+ "webadorsite",
"wiardweb",
"withgoogle",
"withyoutube",
"wixsite",
+ "wixstudio",
"woltlab-demo",
"workisboring",
"wpdevcloud",
@@ -11901,39 +12740,57 @@ var nodeLabels = [...]string{
"za",
"dev",
"af-south-1",
+ "airflow",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
+ "ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
+ "ap-southeast-3",
+ "ap-southeast-4",
+ "ap-southeast-5",
"ca-central-1",
+ "ca-west-1",
"compute",
"compute-1",
"elb",
"eu-central-1",
+ "eu-central-2",
"eu-north-1",
"eu-south-1",
+ "eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
+ "il-central-1",
+ "me-central-1",
"me-south-1",
"s3",
+ "s3-1",
+ "s3-ap-east-1",
"s3-ap-northeast-1",
"s3-ap-northeast-2",
+ "s3-ap-northeast-3",
"s3-ap-south-1",
"s3-ap-southeast-1",
"s3-ap-southeast-2",
"s3-ca-central-1",
"s3-eu-central-1",
+ "s3-eu-north-1",
"s3-eu-west-1",
"s3-eu-west-2",
"s3-eu-west-3",
"s3-external-1",
+ "s3-fips-us-gov-east-1",
"s3-fips-us-gov-west-1",
+ "s3-global",
+ "s3-me-south-1",
"s3-sa-east-1",
"s3-us-east-2",
+ "s3-us-gov-east-1",
"s3-us-gov-west-1",
"s3-us-west-1",
"s3-us-west-2",
@@ -11943,166 +12800,644 @@ var nodeLabels = [...]string{
"s3-website-eu-west-1",
"s3-website-sa-east-1",
"s3-website-us-east-1",
+ "s3-website-us-gov-west-1",
"s3-website-us-west-1",
"s3-website-us-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
+ "us-gov-east-1",
+ "us-gov-west-1",
"us-west-1",
"us-west-2",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "af-south-1",
+ "ap-east-1",
+ "ap-northeast-1",
+ "ap-northeast-2",
+ "ap-northeast-3",
+ "ap-south-1",
+ "ap-south-2",
+ "ap-southeast-1",
+ "ap-southeast-2",
+ "ap-southeast-3",
+ "ap-southeast-4",
+ "ca-central-1",
+ "ca-west-1",
+ "eu-central-1",
+ "eu-central-2",
+ "eu-north-1",
+ "eu-south-1",
+ "eu-south-2",
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3",
+ "il-central-1",
+ "me-central-1",
+ "me-south-1",
+ "sa-east-1",
+ "us-east-1",
+ "us-east-2",
+ "us-west-1",
+ "us-west-2",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
"analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-deprecated",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
+ "analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
"analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-deprecated",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
+ "aws-cloud9",
+ "cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "webview-assets",
+ "vfs",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-website",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "accesspoint",
+ "mrap",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-website",
"analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-deprecated",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
"analytics-gateway",
"aws-cloud9",
"cloud9",
"dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
"s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-deprecated",
+ "s3-fips",
+ "s3-object-lambda",
"s3-website",
"webview-assets",
"vfs",
"webview-assets",
"s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
"analytics-gateway",
"aws-cloud9",
"cloud9",
+ "dualstack",
+ "emrappui-prod",
+ "emrnotebooks-prod",
+ "emrstudio-prod",
+ "execute-api",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-deprecated",
+ "s3-fips",
+ "s3-object-lambda",
+ "s3-website",
"webview-assets",
"vfs",
"webview-assets",
+ "s3",
+ "s3-accesspoint",
+ "s3-accesspoint-fips",
+ "s3-fips",
+ "s3-website",
+ "af-south-1",
+ "ap-east-1",
+ "ap-northeast-1",
+ "ap-northeast-2",
+ "ap-northeast-3",
+ "ap-south-1",
+ "ap-south-2",
+ "ap-southeast-1",
+ "ap-southeast-2",
+ "ap-southeast-3",
+ "ap-southeast-4",
+ "ca-central-1",
+ "ca-west-1",
+ "eu-central-1",
+ "eu-central-2",
+ "eu-north-1",
+ "eu-south-1",
+ "eu-south-2",
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3",
+ "il-central-1",
+ "me-central-1",
+ "me-south-1",
+ "sa-east-1",
+ "us-east-1",
+ "us-east-2",
+ "us-gov-west-1",
+ "us-west-1",
+ "us-west-2",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth",
+ "auth-fips",
+ "auth",
+ "auth-fips",
+ "auth-fips",
+ "auth",
+ "auth-fips",
+ "auth",
+ "auth-fips",
"r",
- "alpha",
- "beta",
+ "services",
"builder",
"dev-builder",
"stg-builder",
@@ -12111,20 +13446,29 @@ var nodeLabels = [...]string{
"ocs",
"demo",
"instance",
+ "staging",
+ "af-south-1",
+ "ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
+ "ap-southeast-3",
"ca-central-1",
"eu-central-1",
+ "eu-north-1",
+ "eu-south-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
+ "il-central-1",
+ "me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
+ "us-gov-east-1",
"us-gov-west-1",
"us-west-1",
"us-west-2",
@@ -12147,7 +13491,6 @@ var nodeLabels = [...]string{
"members",
"nodebalancer",
"ip",
- "app",
"paas",
"eu",
"caracal",
@@ -12162,15 +13505,20 @@ var nodeLabels = [...]string{
"x",
"yali",
"cloud",
+ "o365",
"static",
"xen",
"eu",
"app",
+ "platform",
+ "code-builder-stg",
+ "test",
+ "001",
"api",
+ "media",
"site",
"pro",
"jed",
- "lon",
"ryd",
"pages",
"js",
@@ -12190,23 +13538,34 @@ var nodeLabels = [...]string{
"sa",
"com",
"edu",
- "gov",
+ "gob",
"inf",
+ "nat",
"net",
"org",
- "blogspot",
"com",
"edu",
+ "id",
"int",
+ "net",
"nome",
"org",
+ "publ",
"com",
"edu",
"net",
"org",
+ "assessments",
"ath",
+ "calculators",
+ "cloudns",
+ "funnels",
"gov",
"info",
+ "paynow",
+ "quizzes",
+ "researched",
+ "tests",
"ac",
"biz",
"com",
@@ -12219,15 +13578,15 @@ var nodeLabels = [...]string{
"press",
"pro",
"tm",
- "blogspot",
"scaleforce",
"j",
- "blogspot",
"co",
+ "contentproxy9",
"e4",
"metacentrum",
"muni",
"realm",
+ "rsc",
"cloud",
"custom",
"cloud",
@@ -12238,7 +13597,6 @@ var nodeLabels = [...]string{
"2ix",
"4lima",
"barsy",
- "blogspot",
"bplaced",
"com",
"community-pro",
@@ -12287,6 +13645,8 @@ var nodeLabels = [...]string{
"my-wan",
"myhome-server",
"myspreadshop",
+ "rub",
+ "ruhr-uni-bochum",
"schulplattform",
"schulserver",
"spdns",
@@ -12302,6 +13662,7 @@ var nodeLabels = [...]string{
"uberspace",
"virtual-user",
"virtualuser",
+ "webspaceconfig",
"xn--gnstigbestellen-zvb",
"xn--gnstigliefern-wob",
"dyn",
@@ -12310,44 +13671,106 @@ var nodeLabels = [...]string{
"dyn",
"it",
"pages",
+ "pages-research",
+ "noc",
+ "io",
"customer",
"bss",
- "autocode",
- "curv",
+ "graphic",
+ "12chars",
+ "barsy",
+ "botdash",
+ "crm",
"deno",
"deno-staging",
"deta",
+ "evervault",
"fly",
"gateway",
"githubpreview",
+ "hrsn",
+ "inbrowser",
+ "is-a",
+ "is-a-fullstack",
+ "is-a-good",
+ "is-cool",
+ "is-not-a",
"iserv",
"lcl",
"lclstage",
"localcert",
+ "localplayer",
"loginline",
"mediatech",
+ "modx",
+ "myaddr",
"ngrok",
"ngrok-free",
"pages",
+ "panel",
"platter-app",
"r2",
- "shiftcrypto",
+ "replit",
+ "runcontainers",
"stg",
"stgstage",
"vercel",
"webhare",
"workers",
+ "xmit",
+ "d",
+ "w",
+ "wa",
+ "wb",
+ "wc",
+ "wd",
+ "we",
+ "wf",
+ "relay",
+ "psl",
+ "sub",
+ "wc",
+ "ignored",
+ "sub",
+ "ignored",
"user",
+ "archer",
+ "bones",
+ "canary",
+ "global",
+ "hacker",
+ "id",
+ "janeway",
+ "kim",
+ "kira",
+ "kirk",
+ "odo",
+ "paris",
+ "picard",
+ "pike",
+ "prerelease",
+ "reed",
+ "riker",
+ "sisko",
+ "spock",
+ "staging",
+ "sulu",
+ "tarpit",
+ "teams",
+ "tucker",
+ "wesley",
+ "worf",
"cloudapps",
"london",
+ "libp2p",
"123hjemmeside",
"biz",
- "blogspot",
"co",
"firm",
"myspreadshop",
"reg",
"store",
+ "co",
"com",
"edu",
"gov",
@@ -12373,8 +13796,6 @@ var nodeLabels = [...]string{
"pol",
"soc",
"tm",
- "dapps",
- "bzz",
"base",
"com",
"edu",
@@ -12402,17 +13823,26 @@ var nodeLabels = [...]string{
"org",
"pri",
"riik",
- "blogspot",
+ "ac",
"com",
"edu",
"eun",
"gov",
+ "info",
+ "me",
"mil",
"name",
"net",
"org",
"sci",
- "blogspot",
+ "sport",
+ "tv",
+ "crisp",
+ "tawk",
+ "tawkto",
+ "on",
+ "p",
+ "p",
"123miweb",
"com",
"edu",
@@ -12420,7 +13850,6 @@ var nodeLabels = [...]string{
"myspreadshop",
"nom",
"org",
- "blogspot",
"compute",
"biz",
"com",
@@ -12435,20 +13864,16 @@ var nodeLabels = [...]string{
"cloudns",
"diskstation",
"dogado",
- "mycd",
"spdns",
"transurl",
- "wellbeingzone",
"jelastic",
"party",
"user",
"co",
"koobin",
- "ybo",
"storj",
"123kotisivu",
"aland",
- "blogspot",
"cloudplatform",
"datacenter",
"dy",
@@ -12481,7 +13906,6 @@ var nodeLabels = [...]string{
"asso",
"avocat",
"avoues",
- "blogspot",
"cci",
"chambagri",
"chirurgiens-dentistes",
@@ -12494,7 +13918,6 @@ var nodeLabels = [...]string{
"fbxos",
"freebox-os",
"freeboxos",
- "geometre-expert",
"goupile",
"gouv",
"greta",
@@ -12510,22 +13933,26 @@ var nodeLabels = [...]string{
"tm",
"veterinaire",
"ynh",
+ "pley",
+ "sheezy",
+ "pages",
"edu",
"gov",
"cnpy",
"com",
"edu",
"gov",
- "mil",
"net",
"org",
"pvt",
+ "school",
+ "botdash",
"co",
- "cya",
"kaas",
"net",
"org",
"panel",
+ "stackit",
"daemon",
"com",
"edu",
@@ -12544,7 +13971,6 @@ var nodeLabels = [...]string{
"edu",
"net",
"org",
- "xx",
"ac",
"com",
"edu",
@@ -12554,14 +13980,13 @@ var nodeLabels = [...]string{
"cloud",
"translate",
"usercontent",
- "app",
"asso",
"com",
"edu",
"mobi",
"net",
"org",
- "blogspot",
+ "barsy",
"com",
"edu",
"gov",
@@ -12569,16 +13994,13 @@ var nodeLabels = [...]string{
"org",
"simplesite",
"discourse",
- "blog",
"com",
- "de",
"edu",
"gob",
"ind",
"mil",
"net",
"org",
- "to",
"com",
"edu",
"gov",
@@ -12587,7 +14009,6 @@ var nodeLabels = [...]string{
"net",
"org",
"web",
- "be",
"co",
"com",
"edu",
@@ -12595,7 +14016,6 @@ var nodeLabels = [...]string{
"net",
"org",
"hra",
- "blogspot",
"com",
"edu",
"gov",
@@ -12604,7 +14024,6 @@ var nodeLabels = [...]string{
"ltd",
"net",
"org",
- "secaas",
"xn--55qx5d",
"xn--ciqpn",
"xn--gmq050i",
@@ -12620,7 +14039,6 @@ var nodeLabels = [...]string{
"xn--uc0ay4a",
"xn--wcvs22d",
"xn--zf0avx",
- "cc",
"com",
"edu",
"gob",
@@ -12635,16 +14053,16 @@ var nodeLabels = [...]string{
"jele",
"mircloud",
"myfast",
- "pcloud",
"tempurl",
+ "wp2",
"wpmudev",
"opencraft",
- "blogspot",
+ "brendly",
"com",
- "free",
"from",
"iz",
"name",
+ "shop",
"adult",
"art",
"asso",
@@ -12661,10 +14079,10 @@ var nodeLabels = [...]string{
"pol",
"pro",
"rel",
+ "rt",
"shop",
"2000",
"agrar",
- "blogspot",
"bolt",
"casino",
"city",
@@ -12698,8 +14116,6 @@ var nodeLabels = [...]string{
"biz",
"co",
"desa",
- "flap",
- "forte",
"go",
"mil",
"my",
@@ -12708,9 +14124,7 @@ var nodeLabels = [...]string{
"ponpes",
"sch",
"web",
- "blogspot",
- "rss",
- "blogspot",
+ "zone",
"gov",
"myspreadshop",
"ac",
@@ -12721,7 +14135,6 @@ var nodeLabels = [...]string{
"muni",
"net",
"org",
- "blogspot",
"mytabit",
"ravpage",
"tabitorder",
@@ -12730,7 +14143,6 @@ var nodeLabels = [...]string{
"com",
"net",
"org",
- "ro",
"tt",
"tv",
"ltd",
@@ -12743,7 +14155,6 @@ var nodeLabels = [...]string{
"barsy",
"bihar",
"biz",
- "blogspot",
"business",
"ca",
"cloudns",
@@ -12797,9 +14208,12 @@ var nodeLabels = [...]string{
"ilovecollege",
"knowsitall",
"mayfirst",
+ "mittwald",
+ "mittwaldserver",
"no-ip",
"nsupdate",
"selfip",
+ "typo3server",
"v-info",
"webhop",
"eu",
@@ -12807,8 +14221,6 @@ var nodeLabels = [...]string{
"apigee",
"azurecontainer",
"b-data",
- "backplaneapp",
- "banzaicloud",
"barsy",
"basicserver",
"beagleboard",
@@ -12818,68 +14230,76 @@ var nodeLabels = [...]string{
"bitbucket",
"bluebite",
"boxfuse",
+ "brave",
"browsersafetymark",
+ "bubble",
+ "bubbleapps",
"cleverapps",
+ "cloudbeesusercontent",
+ "co",
"com",
"dappnode",
+ "darklang",
"dedyn",
"definima",
- "drud",
- "dyn53",
"editorx",
+ "edu",
"edugit",
"fh-muenster",
"forgerock",
- "ghost",
"github",
"gitlab",
+ "gov",
"hasura-app",
"hostyhosting",
+ "hypernode",
"hzc",
"jele",
"lair",
"loginline",
"lolipop",
+ "mil",
"mo-siemens",
"moonscale",
"musician",
+ "myaddr",
+ "myrdbx",
+ "net",
"ngrok",
- "nid",
"nodeart",
+ "nom",
"on-acorn",
"on-k3s",
"on-rio",
+ "org",
"pantheonsite",
"protonet",
"pstmn",
"qcx",
"qoto",
+ "rb-hosting",
"readthedocs",
"resindevice",
"resinstaging",
- "s5y",
"sandcats",
- "shiftcrypto",
- "shiftedit",
+ "scrypted",
"shw",
- "spacekit",
"stolos",
"telebit",
"thingdust",
"tickets",
- "unispace",
- "upli",
"utwente",
"vaporcloud",
- "vbrplsbx",
"virtualserver",
+ "webflow",
+ "webflowtest",
"webthings",
- "wedeploy",
- "app",
- "backyards",
+ "wixstudio",
"paas",
"sekd1",
"uk0",
+ "s",
+ "cdn",
"dyndns",
"id",
"apps",
@@ -12893,7 +14313,9 @@ var nodeLabels = [...]string{
"stage",
"mock",
"sys",
+ "site",
"devices",
+ "client",
"dev",
"disrec",
"prod",
@@ -12905,8 +14327,6 @@ var nodeLabels = [...]string{
"cust",
"cust",
"reservd",
- "cloud-fr1",
- "g",
"com",
"edu",
"gov",
@@ -12914,6 +14334,7 @@ var nodeLabels = [...]string{
"net",
"org",
"ac",
+ "arvanedge",
"co",
"gov",
"id",
@@ -12922,15 +14343,8 @@ var nodeLabels = [...]string{
"sch",
"xn--mgba3a4f16a",
"xn--mgba3a4fra",
- "blogspot",
- "com",
- "cupcake",
- "edu",
- "gov",
- "int",
- "net",
- "org",
"123homepage",
+ "12chars",
"16-b",
"32-b",
"64-b",
@@ -12980,7 +14394,6 @@ var nodeLabels = [...]string{
"bi",
"biella",
"bl",
- "blogspot",
"bn",
"bo",
"bologna",
@@ -13235,7 +14648,6 @@ var nodeLabels = [...]string{
"tempioolbia",
"teramo",
"terni",
- "tim",
"tn",
"to",
"torino",
@@ -13348,21 +14760,28 @@ var nodeLabels = [...]string{
"xn--valleaoste-e7a",
"xn--valledaoste-ebb",
"jc",
- "open",
- "jelastic",
- "cloud",
"co",
"net",
"of",
"org",
+ "agri",
+ "ai",
"com",
"edu",
+ "eng",
+ "fm",
"gov",
"mil",
- "name",
"net",
"org",
+ "per",
+ "phd",
"sch",
+ "tv",
+ "0am",
+ "0g0",
+ "0j0",
+ "0t0",
"2-d",
"ac",
"ad",
@@ -13375,7 +14794,6 @@ var nodeLabels = [...]string{
"backdrop",
"bambina",
"bitter",
- "blogspot",
"blush",
"bona",
"boo",
@@ -13432,6 +14850,9 @@ var nodeLabels = [...]string{
"hacca",
"halfmoon",
"handcrafted",
+ "hateblo",
+ "hatenablog",
+ "hatenadiary",
"heavy",
"her",
"hiho",
@@ -13479,6 +14900,7 @@ var nodeLabels = [...]string{
"mond",
"mongolian",
"moo",
+ "mydns",
"nagano",
"nagasaki",
"nagoya",
@@ -13505,6 +14927,7 @@ var nodeLabels = [...]string{
"penne",
"pepper",
"perma",
+ "pgw",
"pigboat",
"pinoko",
"punyu",
@@ -13559,6 +14982,7 @@ var nodeLabels = [...]string{
"weblike",
"websozai",
"whitesnow",
+ "wjg",
"xii",
"xn--0trq7p7nn",
"xn--1ctwo",
@@ -15318,18 +16742,12 @@ var nodeLabels = [...]string{
"ne",
"or",
"sc",
- "blogspot",
- "blog",
"com",
"edu",
"gov",
- "io",
- "jp",
"mil",
"net",
"org",
- "tv",
- "uk",
"us",
"biz",
"com",
@@ -15366,13 +16784,15 @@ var nodeLabels = [...]string{
"rep",
"tra",
"ac",
- "blogspot",
+ "ai",
"busan",
+ "c01",
"chungbuk",
"chungnam",
"co",
"daegu",
"daejeon",
+ "eliv-dns",
"es",
"gangwon",
"go",
@@ -15382,10 +16802,13 @@ var nodeLabels = [...]string{
"gyeongnam",
"hs",
"incheon",
+ "io",
+ "it",
"jeju",
"jeonbuk",
"jeonnam",
"kg",
+ "me",
"mil",
"ms",
"ne",
@@ -15412,13 +16835,10 @@ var nodeLabels = [...]string{
"edu",
"gov",
"jcloud",
- "kazteleport",
"mil",
"net",
"org",
- "upaas",
"bnr",
- "c",
"com",
"edu",
"gov",
@@ -15427,9 +16847,6 @@ var nodeLabels = [...]string{
"net",
"org",
"per",
- "static",
- "dev",
- "sites",
"com",
"edu",
"gov",
@@ -15442,11 +16859,15 @@ var nodeLabels = [...]string{
"net",
"org",
"oy",
- "blogspot",
- "caa",
"cyon",
"dweb",
+ "inbrowser",
+ "myfritz",
"mypep",
+ "nftstorage",
+ "ipfs",
+ "aem",
+ "ewp",
"hlx",
"ac",
"assn",
@@ -15472,17 +16893,14 @@ var nodeLabels = [...]string{
"ac",
"biz",
"co",
- "de",
"edu",
"gov",
"info",
"net",
"org",
"sc",
- "blogspot",
"gov",
"123website",
- "blogspot",
"asn",
"com",
"conf",
@@ -15507,20 +16925,15 @@ var nodeLabels = [...]string{
"net",
"org",
"press",
- "router",
"asso",
"tm",
- "at",
- "blogspot",
- "de",
- "jp",
- "to",
+ "ir",
"ac",
"barsy",
"brasilia",
"c66",
"co",
- "daplie",
+ "craft",
"ddns",
"diskstation",
"dnsfor",
@@ -15528,11 +16941,6 @@ var nodeLabels = [...]string{
"edgestack",
"edu",
"filegear",
- "filegear-au",
- "filegear-de",
- "filegear-gb",
- "filegear-ie",
- "filegear-jp",
"filegear-sg",
"glitch",
"gov",
@@ -15542,26 +16950,22 @@ var nodeLabels = [...]string{
"loginto",
"lohmus",
"mcdir",
- "mcpe",
"myds",
"net",
"nohost",
"noip",
"org",
"priv",
- "ravendb",
"soundcast",
"synology",
"tcp4",
"transip",
"vp4",
"webhop",
- "wedeploy",
- "yombo",
- "localhost",
"site",
"framer",
"barsy",
+ "barsyonline",
"co",
"com",
"edu",
@@ -15570,8 +16974,6 @@ var nodeLabels = [...]string{
"nom",
"org",
"prd",
- "tm",
- "blogspot",
"com",
"edu",
"gov",
@@ -15579,12 +16981,18 @@ var nodeLabels = [...]string{
"name",
"net",
"org",
+ "ac",
+ "art",
+ "asso",
"com",
"edu",
"gouv",
"gov",
+ "info",
+ "inst",
"net",
"org",
+ "pr",
"presse",
"edu",
"gov",
@@ -15597,13 +17005,12 @@ var nodeLabels = [...]string{
"org",
"barsy",
"dscloud",
+ "ind",
"ju",
- "blogspot",
"gov",
"com",
"edu",
"gov",
- "lab",
"minisite",
"net",
"org",
@@ -15611,7 +17018,6 @@ var nodeLabels = [...]string{
"edu",
"net",
"org",
- "blogspot",
"ac",
"co",
"com",
@@ -15641,17 +17047,14 @@ var nodeLabels = [...]string{
"edu",
"gov",
"int",
- "museum",
"net",
"org",
- "blogspot",
"com",
"edu",
"gob",
"net",
"org",
"biz",
- "blogspot",
"com",
"edu",
"gov",
@@ -15667,23 +17070,12 @@ var nodeLabels = [...]string{
"mil",
"net",
"org",
- "ca",
- "cc",
+ "alt",
"co",
"com",
- "dr",
- "in",
- "info",
- "mobi",
- "mx",
- "name",
- "or",
+ "gov",
+ "net",
"org",
- "pro",
- "school",
- "tv",
- "us",
- "ws",
"her",
"his",
"forgot",
@@ -15705,20 +17097,21 @@ var nodeLabels = [...]string{
"akamaized",
"akamaized-staging",
"alwaysdata",
+ "apps-1and1",
"appudo",
"at-band-camp",
"atlassian-dev",
+ "azure-api",
"azure-mobile",
+ "azureedge",
+ "azurefd",
"azurestaticapps",
"azurewebsites",
- "bar0",
- "bar1",
- "bar2",
"barsy",
- "bitbridge",
"blackbaudcdn",
"blogdns",
"boomla",
+ "botdash",
"bounceme",
"bplaced",
"broke-it",
@@ -15731,16 +17124,25 @@ var nodeLabels = [...]string{
"clickrising",
"cloudaccess",
"cloudapp",
+ "cloudflare",
+ "cloudflareanycast",
+ "cloudflarecn",
+ "cloudflareglobal",
"cloudfront",
"cloudfunctions",
"cloudjiffy",
"cloudycluster",
"community-pro",
"cryptonomic",
+ "ctfcloud",
"dattolocal",
"ddns",
+ "ddns-ip",
"debian",
"definima",
+ "deno",
+ "dns-cloud",
+ "dns-dynamic",
"dnsalias",
"dnsdojo",
"dnsup",
@@ -15762,16 +17164,16 @@ var nodeLabels = [...]string{
"familyds",
"fastly",
"fastlylb",
- "faststacks",
"feste-ip",
"firewall-gateway",
- "flynnhosting",
"from-az",
"from-co",
"from-la",
"from-ny",
"gb",
"gets-it",
+ "ggff",
+ "grafana-dev",
"ham-radio-op",
"heteml",
"hicam",
@@ -15790,10 +17192,16 @@ var nodeLabels = [...]string{
"is-a-geek",
"isa-geek",
"jp",
+ "keyword-on",
"kicks-ass",
"kinghost",
"knx-server",
"krellian",
+ "live-on",
+ "localcert",
+ "localhostcert",
+ "localto",
+ "mafelo",
"massivegrid",
"meinforum",
"memset",
@@ -15804,7 +17212,9 @@ var nodeLabels = [...]string{
"myeffect",
"myfritz",
"mymediapc",
+ "mynetname",
"mypsx",
+ "myradweb",
"mysecuritycamera",
"myspreadshop",
"nhlfan",
@@ -15812,6 +17222,7 @@ var nodeLabels = [...]string{
"now-dns",
"office-on-the",
"onavstack",
+ "oninferno",
"ovh",
"ownip",
"pgafan",
@@ -15820,6 +17231,7 @@ var nodeLabels = [...]string{
"rackmaze",
"redirectme",
"reserve-online",
+ "routingthecloud",
"ru",
"saveincloud",
"scaleforce",
@@ -15834,26 +17246,28 @@ var nodeLabels = [...]string{
"serveblog",
"serveftp",
"serveminecraft",
+ "server-on",
"shopselect",
"siteleaf",
"square7",
"squares",
"srcf",
"static-access",
+ "subsc-pay",
"supabase",
"sytes",
- "t3l3p0rt",
- "tailscale",
"thruhere",
"torproject",
+ "trafficmanager",
"ts",
- "tsukaeru",
"twmail",
"uk",
"uni5",
"vpndns",
"vps-host",
+ "vusercontent",
"webhop",
+ "windows",
"yandexcloud",
"za",
"prod",
@@ -15861,6 +17275,10 @@ var nodeLabels = [...]string{
"1",
"2",
"3",
+ "4",
+ "5",
+ "6",
+ "7",
"centralus",
"eastasia",
"eastus2",
@@ -15868,6 +17286,10 @@ var nodeLabels = [...]string{
"westus2",
"r",
"u",
+ "cdn",
+ "cdn",
+ "cdn",
+ "cdn",
"fra1-de",
"west1-us",
"jls-sto1",
@@ -15890,6 +17312,7 @@ var nodeLabels = [...]string{
"ny-1",
"ny-2",
"sg-1",
+ "sn",
"hosting",
"webpaas",
"jelastic",
@@ -15897,13 +17320,15 @@ var nodeLabels = [...]string{
"j",
"soc",
"user",
- "beta",
"pages",
- "jelastic",
+ "c",
"jelastic",
"atl",
"njs",
"ric",
+ "core",
+ "servicebus",
+ "blob",
"storage",
"website",
"alces",
@@ -15922,6 +17347,7 @@ var nodeLabels = [...]string{
"rec",
"store",
"web",
+ "biz",
"col",
"com",
"edu",
@@ -15936,8 +17362,13 @@ var nodeLabels = [...]string{
"net",
"ngo",
"org",
+ "plc",
"sch",
- "blogspot",
+ "co",
+ "dl",
+ "go",
+ "lg",
+ "on",
"ac",
"biz",
"co",
@@ -15953,7 +17384,6 @@ var nodeLabels = [...]string{
"org",
"web",
"123website",
- "blogspot",
"cistron",
"co",
"demon",
@@ -16027,7 +17457,6 @@ var nodeLabels = [...]string{
"bjarkoy",
"bjerkreim",
"bjugn",
- "blogspot",
"bodo",
"bokn",
"bomlo",
@@ -16742,6 +18171,7 @@ var nodeLabels = [...]string{
"mine",
"shacknet",
"ac",
+ "cloudns",
"co",
"cri",
"geek",
@@ -16757,7 +18187,6 @@ var nodeLabels = [...]string{
"parliament",
"school",
"xn--mori-qsa",
- "blogspot",
"co",
"com",
"edu",
@@ -16767,18 +18196,17 @@ var nodeLabels = [...]string{
"net",
"org",
"pro",
- "homelink",
- "onred",
+ "kin",
"service",
- "staging",
+ "obl",
"barsy",
"eero",
"eero-stage",
+ "websitebuilder",
"tech",
"accesscam",
"ae",
"altervista",
- "amune",
"barsy",
"blogdns",
"blogsite",
@@ -16788,7 +18216,6 @@ var nodeLabels = [...]string{
"camdvr",
"cdn77",
"cdn77-secure",
- "certmgr",
"cloudns",
"collegefan",
"couchpotatofries",
@@ -16799,6 +18226,7 @@ var nodeLabels = [...]string{
"doesntexist",
"dontexist",
"doomdns",
+ "dpdns",
"dsmynas",
"duckdns",
"dvrdns",
@@ -16817,6 +18245,7 @@ var nodeLabels = [...]string{
"from-me",
"game-host",
"gotdns",
+ "hatenadiary",
"hepforge",
"hk",
"hobby-site",
@@ -16828,6 +18257,7 @@ var nodeLabels = [...]string{
"httpbin",
"in-dsl",
"in-vpn",
+ "ip-dynamic",
"is-a-bruinsfan",
"is-a-candidate",
"is-a-celticsfan",
@@ -16838,6 +18268,7 @@ var nodeLabels = [...]string{
"is-a-patsfan",
"is-a-soxfan",
"is-found",
+ "is-local",
"is-lost",
"is-saved",
"is-very-bad",
@@ -16852,7 +18283,6 @@ var nodeLabels = [...]string{
"mayfirst",
"misconfused",
"mlbfan",
- "mozilla-iot",
"my-firewall",
"myfirewall",
"myftp",
@@ -16868,6 +18298,7 @@ var nodeLabels = [...]string{
"pubtls",
"read-books",
"readmyblog",
+ "routingthecloud",
"selfip",
"sellsyourhome",
"servebbs",
@@ -16890,7 +18321,6 @@ var nodeLabels = [...]string{
"wmflabs",
"za",
"zapto",
- "tele",
"c",
"rsc",
"origin",
@@ -16930,7 +18360,6 @@ var nodeLabels = [...]string{
"lt",
"lu",
"lv",
- "mc",
"me",
"mk",
"mt",
@@ -16940,10 +18369,8 @@ var nodeLabels = [...]string{
"nl",
"no",
"nz",
- "paris",
"pl",
"pt",
- "q-a",
"ro",
"ru",
"se",
@@ -16971,17 +18398,16 @@ var nodeLabels = [...]string{
"nom",
"org",
"sld",
+ "aem",
"codeberg",
+ "heyflow",
"hlx",
"hlx3",
- "magnet",
"pdns",
"plesk",
"prvcy",
"rocky",
"translated",
- "ybo",
- "blogspot",
"com",
"edu",
"gob",
@@ -16992,6 +18418,7 @@ var nodeLabels = [...]string{
"com",
"edu",
"org",
+ "cloudns",
"com",
"edu",
"gov",
@@ -17003,17 +18430,18 @@ var nodeLabels = [...]string{
"framer",
"1337",
"ngrok",
+ "ac",
"biz",
"com",
"edu",
"fam",
+ "gkp",
"gob",
+ "gog",
"gok",
- "gon",
"gop",
"gos",
"gov",
- "info",
"net",
"org",
"web",
@@ -17030,16 +18458,20 @@ var nodeLabels = [...]string{
"bialowieza",
"bialystok",
"bielawa",
+ "bielsko",
"bieszczady",
"biz",
"boleslawiec",
"bydgoszcz",
"bytom",
+ "cfolks",
"cieszyn",
"co",
"com",
"czeladz",
"czest",
+ "dfirma",
+ "dkonto",
"dlugoleka",
"ecommerce-shop",
"edu",
@@ -17084,6 +18516,7 @@ var nodeLabels = [...]string{
"legnica",
"lezajsk",
"limanowa",
+ "lodz",
"lomza",
"lowicz",
"lubartow",
@@ -17120,9 +18553,11 @@ var nodeLabels = [...]string{
"ostroleka",
"ostrowiec",
"ostrowwlkp",
+ "pabianice",
"pc",
"pila",
"pisz",
+ "plock",
"podhale",
"podlasie",
"polkowice",
@@ -17148,7 +18583,9 @@ var nodeLabels = [...]string{
"sex",
"shop",
"shoparena",
+ "sieradz",
"simplesite",
+ "skierniewice",
"sklep",
"skoczow",
"slask",
@@ -17171,6 +18608,7 @@ var nodeLabels = [...]string{
"tarnobrzeg",
"tgory",
"tm",
+ "torun",
"tourism",
"travel",
"turek",
@@ -17190,10 +18628,12 @@ var nodeLabels = [...]string{
"wolomin",
"wroc",
"wroclaw",
+ "you2",
"zachpomor",
"zagan",
"zakopane",
"zarow",
+ "zgierz",
"zgora",
"zgorzelec",
"ap",
@@ -17259,7 +18699,6 @@ var nodeLabels = [...]string{
"gov",
"net",
"org",
- "indie",
"ac",
"biz",
"com",
@@ -17273,6 +18712,7 @@ var nodeLabels = [...]string{
"org",
"pro",
"prof",
+ "12chars",
"aaa",
"aca",
"acct",
@@ -17281,13 +18721,12 @@ var nodeLabels = [...]string{
"barsy",
"cloudns",
"cpa",
- "dnstrace",
"eng",
"jur",
"law",
"med",
+ "ngrok",
"recht",
- "bci",
"com",
"edu",
"gov",
@@ -17296,7 +18735,6 @@ var nodeLabels = [...]string{
"plo",
"sec",
"123paginaweb",
- "blogspot",
"com",
"edu",
"gov",
@@ -17306,13 +18744,10 @@ var nodeLabels = [...]string{
"org",
"publ",
"barsy",
- "belau",
+ "id",
+ "kin",
"cloudns",
- "co",
- "ed",
- "go",
- "ne",
- "or",
+ "gov",
"x443",
"com",
"coop",
@@ -17321,7 +18756,6 @@ var nodeLabels = [...]string{
"mil",
"net",
"org",
- "blogspot",
"com",
"edu",
"gov",
@@ -17331,14 +18765,12 @@ var nodeLabels = [...]string{
"org",
"sch",
"asso",
- "blogspot",
+ "can",
"com",
- "nom",
- "ybo",
+ "netlib",
"clan",
"arts",
"barsy",
- "blogspot",
"co",
"com",
"firm",
@@ -17353,9 +18785,10 @@ var nodeLabels = [...]string{
"www",
"lima-city",
"myddns",
+ "stackit",
"webspace",
"ac",
- "blogspot",
+ "barsy",
"brendly",
"co",
"edu",
@@ -17363,14 +18796,11 @@ var nodeLabels = [...]string{
"in",
"org",
"ox",
- "ua",
"shop",
- "123sait",
"ac",
"adygeya",
"bashkiria",
"bir",
- "blogspot",
"cbg",
"cldmail",
"com",
@@ -17400,9 +18830,7 @@ var nodeLabels = [...]string{
"pp",
"pyatigorsk",
"ras",
- "regruhosting",
"spb",
- "test",
"vladikavkaz",
"vladimir",
"hb",
@@ -17411,17 +18839,22 @@ var nodeLabels = [...]string{
"landing",
"spectrum",
"vps",
- "jelastic",
"build",
"code",
"database",
"development",
- "hs",
+ "liara",
"migration",
"onporter",
"ravendb",
"repl",
"servers",
+ "stackit",
+ "val",
+ "wix",
+ "iran",
+ "express",
+ "web",
"ac",
"co",
"coop",
@@ -17447,8 +18880,6 @@ var nodeLabels = [...]string{
"gov",
"net",
"org",
- "ybo",
- "edu",
"gov",
"service",
"com",
@@ -17464,11 +18895,9 @@ var nodeLabels = [...]string{
"ac",
"b",
"bd",
- "blogspot",
"brand",
"c",
"com",
- "conf",
"d",
"e",
"f",
@@ -17493,7 +18922,6 @@ var nodeLabels = [...]string{
"o",
"org",
"p",
- "paba",
"parti",
"pp",
"press",
@@ -17506,17 +18934,14 @@ var nodeLabels = [...]string{
"x",
"y",
"z",
- "su",
"loginline",
- "blogspot",
"com",
"edu",
"enscaled",
"gov",
"net",
"org",
- "per",
- "bip",
+ "botda",
"com",
"gov",
"hashbang",
@@ -17525,43 +18950,46 @@ var nodeLabels = [...]string{
"now",
"org",
"platform",
- "vxl",
- "wedeploy",
- "bc",
"ent",
"eu",
"us",
"barsy",
+ "barsyonline",
"base",
"hoplix",
- "blogspot",
+ "shopware",
+ "f5",
"gitapp",
"gitpage",
"barsy",
"byen",
+ "canva",
"cloudera",
+ "convex",
+ "cpanel",
"cyon",
"fastvps",
- "fnwk",
- "folionetwork",
+ "heyflow",
"jele",
- "lelux",
+ "jouwweb",
"loginline",
- "mintere",
+ "madethis",
+ "notion",
"novecore",
"omniwe",
"opensocial",
"platformsh",
+ "square",
"srht",
"tst",
- "blogspot",
+ "wpsquared",
+ "my",
"com",
"edu",
"gov",
"net",
"org",
"art",
- "blogspot",
"com",
"edu",
"gouv",
@@ -17574,12 +19002,17 @@ var nodeLabels = [...]string{
"me",
"net",
"org",
- "sch",
- "diher",
+ "surveys",
+ "app-ionos",
+ "heiyu",
+ "hf",
"myfast",
+ "project",
"uber",
"xs4all",
+ "static",
"biz",
+ "co",
"com",
"edu",
"gov",
@@ -17592,6 +19025,7 @@ var nodeLabels = [...]string{
"consulado",
"edu",
"embaixada",
+ "helioho",
"kirara",
"mil",
"net",
@@ -17600,6 +19034,7 @@ var nodeLabels = [...]string{
"principe",
"saotome",
"store",
+ "barsy",
"sellfy",
"shopware",
"storebase",
@@ -17672,12 +19107,9 @@ var nodeLabels = [...]string{
"ac",
"co",
"org",
- "ch",
- "me",
- "we",
- "blogspot",
"discourse",
"jelastic",
+ "cleverapps",
"co",
"sch",
"ac",
@@ -17733,17 +19165,18 @@ var nodeLabels = [...]string{
"gov",
"mil",
"net",
- "nyan",
"org",
"oya",
"quickconnect",
- "rdv",
"vpnplus",
"x0",
"direct",
"prequalifyme",
- "now-dns",
+ "addr",
+ "myaddr",
+ "dyn",
"ntdll",
+ "wadl",
"av",
"bbs",
"bel",
@@ -17766,33 +19199,24 @@ var nodeLabels = [...]string{
"tsk",
"tv",
"web",
- "blogspot",
"gov",
- "ybo",
- "aero",
"biz",
"co",
"com",
- "coop",
"edu",
"gov",
"info",
- "int",
- "jobs",
- "mobi",
- "museum",
+ "mil",
"name",
"net",
"org",
"pro",
- "travel",
"better-than",
"dyndns",
"from",
"on-the-web",
"sakura",
"worse-than",
- "blogspot",
"club",
"com",
"ebiz",
@@ -17801,12 +19225,10 @@ var nodeLabels = [...]string{
"gov",
"idv",
"mil",
+ "mydns",
"net",
"org",
"url",
- "xn--czrw28b",
- "xn--uc0atv",
- "xn--zf0ao64a",
"mymailer",
"ac",
"co",
@@ -17867,6 +19289,7 @@ var nodeLabels = [...]string{
"lt",
"ltd",
"lugansk",
+ "luhansk",
"lutsk",
"lv",
"lviv",
@@ -17893,12 +19316,14 @@ var nodeLabels = [...]string{
"ternopil",
"uz",
"uzhgorod",
+ "uzhhorod",
"v",
"vinnica",
"vinnytsia",
"vn",
"volyn",
"yalta",
+ "zakarpattia",
"zaporizhzhe",
"zaporizhzhia",
"zhitomir",
@@ -17906,14 +19331,17 @@ var nodeLabels = [...]string{
"zp",
"zt",
"ac",
- "blogspot",
"co",
"com",
+ "edu",
"go",
+ "gov",
+ "mil",
"ne",
"or",
"org",
"sc",
+ "us",
"ac",
"barsy",
"co",
@@ -17930,6 +19358,8 @@ var nodeLabels = [...]string{
"me",
"net",
"nhs",
+ "nimsite",
+ "oraclegovcloudapps",
"org",
"plc",
"police",
@@ -17940,21 +19370,18 @@ var nodeLabels = [...]string{
"adimo",
"barsy",
"barsyonline",
- "blogspot",
"bytemark",
"layershift",
"myspreadshop",
"nh-serv",
"no-ip",
"retrosnub",
- "wellbeingzone",
"dh",
"vm",
"j",
"cust",
"api",
"campaign",
- "homeoffice",
"service",
"affinitylottery",
"glug",
@@ -17974,15 +19401,13 @@ var nodeLabels = [...]string{
"dc",
"de",
"dni",
- "drud",
"enscaled",
- "fed",
"fl",
"freeddns",
"ga",
"golffan",
- "graphox",
"gu",
+ "heliohost",
"hi",
"ia",
"id",
@@ -17990,7 +19415,6 @@ var nodeLabels = [...]string{
"in",
"is-by",
"isa",
- "kids",
"ks",
"ky",
"la",
@@ -18007,6 +19431,7 @@ var nodeLabels = [...]string{
"nc",
"nd",
"ne",
+ "ngo",
"nh",
"nj",
"nm",
@@ -18024,6 +19449,8 @@ var nodeLabels = [...]string{
"ri",
"sc",
"sd",
+ "servername",
+ "srv",
"stuff-4-sale",
"tn",
"tx",
@@ -18063,7 +19490,6 @@ var nodeLabels = [...]string{
"k12",
"lib",
"cc",
- "k12",
"lib",
"phx",
"cc",
@@ -18178,6 +19604,8 @@ var nodeLabels = [...]string{
"lib",
"cc",
"lib",
+ "gh",
+ "gl",
"cc",
"k12",
"lib",
@@ -18212,7 +19640,6 @@ var nodeLabels = [...]string{
"mil",
"net",
"org",
- "blogspot",
"co",
"com",
"net",
@@ -18223,6 +19650,7 @@ var nodeLabels = [...]string{
"gov",
"gv",
"mil",
+ "mydns",
"net",
"org",
"d",
@@ -18232,6 +19660,7 @@ var nodeLabels = [...]string{
"com",
"e12",
"edu",
+ "emprende",
"firm",
"gob",
"gov",
@@ -18246,7 +19675,7 @@ var nodeLabels = [...]string{
"store",
"tec",
"web",
- "at",
+ "edu",
"co",
"com",
"k12",
@@ -18266,7 +19695,6 @@ var nodeLabels = [...]string{
"binhphuoc",
"binhthuan",
"biz",
- "blogspot",
"camau",
"cantho",
"caobang",
@@ -18331,12 +19759,8 @@ var nodeLabels = [...]string{
"vinhlong",
"vinhphuc",
"yenbai",
- "blog",
- "cn",
"com",
- "dev",
"edu",
- "me",
"net",
"org",
"framer",
@@ -18384,11 +19808,8 @@ var nodeLabels = [...]string{
"xn--j1adp",
"xn--j1aef",
"xn--j1ael8b",
- "blogsite",
- "crafting",
- "localzone",
+ "botdash",
"telebit",
- "zapto",
"com",
"edu",
"gov",
@@ -18414,7 +19835,6 @@ var nodeLabels = [...]string{
"school",
"tm",
"web",
- "blogspot",
"ac",
"biz",
"co",
@@ -18427,8 +19847,8 @@ var nodeLabels = [...]string{
"org",
"sch",
"cloud66",
- "hs",
"lima",
+ "stackit",
"triton",
"ac",
"co",
diff --git a/quic/config.go b/quic/config.go
index 5d420312bb..d6aa87730f 100644
--- a/quic/config.go
+++ b/quic/config.go
@@ -11,6 +11,8 @@ import (
"log/slog"
"math"
"time"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// A Config structure configures a QUIC endpoint.
@@ -134,15 +136,15 @@ func (c *Config) maxUniRemoteStreams() int64 {
}
func (c *Config) maxStreamReadBufferSize() int64 {
- return configDefault(c.MaxStreamReadBufferSize, 1<<20, maxVarint)
+ return configDefault(c.MaxStreamReadBufferSize, 1<<20, quicwire.MaxVarint)
}
func (c *Config) maxStreamWriteBufferSize() int64 {
- return configDefault(c.MaxStreamWriteBufferSize, 1<<20, maxVarint)
+ return configDefault(c.MaxStreamWriteBufferSize, 1<<20, quicwire.MaxVarint)
}
func (c *Config) maxConnReadBufferSize() int64 {
- return configDefault(c.MaxConnReadBufferSize, 1<<20, maxVarint)
+ return configDefault(c.MaxConnReadBufferSize, 1<<20, quicwire.MaxVarint)
}
func (c *Config) handshakeTimeout() time.Duration {
diff --git a/quic/conn.go b/quic/conn.go
index bf54409bfe..1f1cfa6d0a 100644
--- a/quic/conn.go
+++ b/quic/conn.go
@@ -186,6 +186,11 @@ func (c *Conn) RemoteAddr() netip.AddrPort {
return c.peerAddr
}
+// ConnectionState returns basic TLS details about the connection.
+func (c *Conn) ConnectionState() tls.ConnectionState {
+ return c.tls.ConnectionState()
+}
+
// confirmHandshake is called when the handshake is confirmed.
// https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2
func (c *Conn) confirmHandshake(now time.Time) {
diff --git a/quic/conn_close.go b/quic/conn_close.go
index 1798d0536f..cd8d7e3c5a 100644
--- a/quic/conn_close.go
+++ b/quic/conn_close.go
@@ -178,7 +178,7 @@ func (c *Conn) sendOK(now time.Time) bool {
}
}
-// sendConnectionClose reports that the conn has sent a CONNECTION_CLOSE to the peer.
+// sentConnectionClose reports that the conn has sent a CONNECTION_CLOSE to the peer.
func (c *Conn) sentConnectionClose(now time.Time) {
switch c.lifetime.state {
case connStatePeerClosed:
@@ -230,6 +230,17 @@ func (c *Conn) setFinalError(err error) {
close(c.lifetime.donec)
}
+// finalError returns the final connection status reported to the user,
+// or nil if a final status has not yet been set.
+func (c *Conn) finalError() error {
+ select {
+ case <-c.lifetime.donec:
+ return c.lifetime.finalErr
+ default:
+ }
+ return nil
+}
+
func (c *Conn) waitReady(ctx context.Context) error {
select {
case <-c.lifetime.readyc:
diff --git a/quic/conn_id.go b/quic/conn_id.go
index 2efe8d6b5d..2d50f14fa6 100644
--- a/quic/conn_id.go
+++ b/quic/conn_id.go
@@ -9,6 +9,7 @@ package quic
import (
"bytes"
"crypto/rand"
+ "slices"
)
// connIDState is a conn's connection IDs.
@@ -25,8 +26,16 @@ type connIDState struct {
remote []remoteConnID
nextLocalSeq int64
- retireRemotePriorTo int64 // largest Retire Prior To value sent by the peer
- peerActiveConnIDLimit int64 // peer's active_connection_id_limit transport parameter
+ peerActiveConnIDLimit int64 // peer's active_connection_id_limit
+
+ // Handling of retirement of remote connection IDs.
+ // The rangesets track ID sequence numbers.
+ // IDs in need of retirement are added to remoteRetiring,
+ // moved to remoteRetiringSent once we send a RETIRE_CONECTION_ID frame,
+ // and removed from the set once retirement completes.
+ retireRemotePriorTo int64 // largest Retire Prior To value sent by the peer
+ remoteRetiring rangeset[int64] // remote IDs in need of retirement
+ remoteRetiringSent rangeset[int64] // remote IDs waiting for ack of retirement
originalDstConnID []byte // expected original_destination_connection_id param
retrySrcConnID []byte // expected retry_source_connection_id param
@@ -45,9 +54,6 @@ type connID struct {
// For the transient destination ID in a client's Initial packet, this is -1.
seq int64
- // retired is set when the connection ID is retired.
- retired bool
-
// send is set when the connection ID's state needs to be sent to the peer.
//
// For local IDs, this indicates a new ID that should be sent
@@ -144,9 +150,7 @@ func (s *connIDState) srcConnID() []byte {
// dstConnID is the Destination Connection ID to use in a sent packet.
func (s *connIDState) dstConnID() (cid []byte, ok bool) {
for i := range s.remote {
- if !s.remote[i].retired {
- return s.remote[i].cid, true
- }
+ return s.remote[i].cid, true
}
return nil, false
}
@@ -154,14 +158,12 @@ func (s *connIDState) dstConnID() (cid []byte, ok bool) {
// isValidStatelessResetToken reports whether the given reset token is
// associated with a non-retired connection ID which we have used.
func (s *connIDState) isValidStatelessResetToken(resetToken statelessResetToken) bool {
- for i := range s.remote {
- // We currently only use the first available remote connection ID,
- // so any other reset token is not valid.
- if !s.remote[i].retired {
- return s.remote[i].resetToken == resetToken
- }
+ if len(s.remote) == 0 {
+ return false
}
- return false
+ // We currently only use the first available remote connection ID,
+ // so any other reset token is not valid.
+ return s.remote[0].resetToken == resetToken
}
// setPeerActiveConnIDLimit sets the active_connection_id_limit
@@ -174,7 +176,7 @@ func (s *connIDState) setPeerActiveConnIDLimit(c *Conn, lim int64) error {
func (s *connIDState) issueLocalIDs(c *Conn) error {
toIssue := min(int(s.peerActiveConnIDLimit), maxPeerActiveConnIDLimit)
for i := range s.local {
- if s.local[i].seq != -1 && !s.local[i].retired {
+ if s.local[i].seq != -1 {
toIssue--
}
}
@@ -271,7 +273,7 @@ func (s *connIDState) handlePacket(c *Conn, ptype packetType, srcConnID []byte)
}
}
case ptype == packetTypeHandshake && c.side == serverSide:
- if len(s.local) > 0 && s.local[0].seq == -1 && !s.local[0].retired {
+ if len(s.local) > 0 && s.local[0].seq == -1 {
// We're a server connection processing the first Handshake packet from
// the client. Discard the transient, client-chosen connection ID used
// for Initial packets; the client will never send it again.
@@ -304,23 +306,29 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
}
}
+ if seq < s.retireRemotePriorTo {
+ // This ID was already retired by a previous NEW_CONNECTION_ID frame.
+ // Nothing to do.
+ return nil
+ }
+
if retire > s.retireRemotePriorTo {
+ // Add newly-retired connection IDs to the set we need to send
+ // RETIRE_CONNECTION_ID frames for, and remove them from s.remote.
+ //
+ // (This might cause us to send a RETIRE_CONNECTION_ID for an ID we've
+ // never seen. That's fine.)
+ s.remoteRetiring.add(s.retireRemotePriorTo, retire)
s.retireRemotePriorTo = retire
+ s.needSend = true
+ s.remote = slices.DeleteFunc(s.remote, func(rcid remoteConnID) bool {
+ return rcid.seq < s.retireRemotePriorTo
+ })
}
have := false // do we already have this connection ID?
- active := 0
for i := range s.remote {
rcid := &s.remote[i]
- if !rcid.retired && rcid.seq >= 0 && rcid.seq < s.retireRemotePriorTo {
- s.retireRemote(rcid)
- c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
- conns.retireResetToken(c, rcid.resetToken)
- })
- }
- if !rcid.retired {
- active++
- }
if rcid.seq == seq {
if !bytes.Equal(rcid.cid, cid) {
return localTransportError{
@@ -329,6 +337,7 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
}
}
have = true // yes, we've seen this sequence number
+ break
}
}
@@ -345,18 +354,12 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
},
resetToken: resetToken,
})
- if seq < s.retireRemotePriorTo {
- // This ID was already retired by a previous NEW_CONNECTION_ID frame.
- s.retireRemote(&s.remote[len(s.remote)-1])
- } else {
- active++
- c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
- conns.addResetToken(c, resetToken)
- })
- }
+ c.endpoint.connsMap.updateConnIDs(func(conns *connsMap) {
+ conns.addResetToken(c, resetToken)
+ })
}
- if active > activeConnIDLimit {
+ if len(s.remote) > activeConnIDLimit {
// Retired connection IDs (including newly-retired ones) do not count
// against the limit.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-5.1.1-5
@@ -370,25 +373,18 @@ func (s *connIDState) handleNewConnID(c *Conn, seq, retire int64, cid []byte, re
// for which RETIRE_CONNECTION_ID frames have not yet been acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6
//
- // Set a limit of four times the active_connection_id_limit for
- // the total number of remote connection IDs we keep state for locally.
- if len(s.remote) > 4*activeConnIDLimit {
+ // Set a limit of three times the active_connection_id_limit for
+ // the total number of remote connection IDs we keep retirement state for.
+ if s.remoteRetiring.size()+s.remoteRetiringSent.size() > 3*activeConnIDLimit {
return localTransportError{
code: errConnectionIDLimit,
- reason: "too many unacknowledged RETIRE_CONNECTION_ID frames",
+ reason: "too many unacknowledged retired connection ids",
}
}
return nil
}
-// retireRemote marks a remote connection ID as retired.
-func (s *connIDState) retireRemote(rcid *remoteConnID) {
- rcid.retired = true
- rcid.send.setUnsent()
- s.needSend = true
-}
-
func (s *connIDState) handleRetireConnID(c *Conn, seq int64) error {
if seq >= s.nextLocalSeq {
return localTransportError{
@@ -424,20 +420,11 @@ func (s *connIDState) ackOrLossNewConnectionID(pnum packetNumber, seq int64, fat
}
func (s *connIDState) ackOrLossRetireConnectionID(pnum packetNumber, seq int64, fate packetFate) {
- for i := 0; i < len(s.remote); i++ {
- if s.remote[i].seq != seq {
- continue
- }
- if fate == packetAcked {
- // We have retired this connection ID, and the peer has acked.
- // Discard its state completely.
- s.remote = append(s.remote[:i], s.remote[i+1:]...)
- } else {
- // RETIRE_CONNECTION_ID frame was lost, mark for retransmission.
- s.needSend = true
- s.remote[i].send.ackOrLoss(pnum, fate)
- }
- return
+ s.remoteRetiringSent.sub(seq, seq+1)
+ if fate == packetLost {
+ // RETIRE_CONNECTION_ID frame was lost, mark for retransmission.
+ s.remoteRetiring.add(seq, seq+1)
+ s.needSend = true
}
}
@@ -469,14 +456,22 @@ func (s *connIDState) appendFrames(c *Conn, pnum packetNumber, pto bool) bool {
}
s.local[i].send.setSent(pnum)
}
- for i := range s.remote {
- if !s.remote[i].send.shouldSendPTO(pto) {
- continue
+ if pto {
+ for _, r := range s.remoteRetiringSent {
+ for cid := r.start; cid < r.end; cid++ {
+ if !c.w.appendRetireConnectionIDFrame(cid) {
+ return false
+ }
+ }
}
- if !c.w.appendRetireConnectionIDFrame(s.remote[i].seq) {
+ }
+ for s.remoteRetiring.numRanges() > 0 {
+ cid := s.remoteRetiring.min()
+ if !c.w.appendRetireConnectionIDFrame(cid) {
return false
}
- s.remote[i].send.setSent(pnum)
+ s.remoteRetiring.sub(cid, cid+1)
+ s.remoteRetiringSent.add(cid, cid+1)
}
s.needSend = false
return true
diff --git a/quic/conn_id_test.go b/quic/conn_id_test.go
index d44472e813..2c3f170160 100644
--- a/quic/conn_id_test.go
+++ b/quic/conn_id_test.go
@@ -664,3 +664,52 @@ func TestConnIDsCleanedUpAfterClose(t *testing.T) {
}
})
}
+
+func TestConnIDRetiredConnIDResent(t *testing.T) {
+ tc := newTestConn(t, serverSide)
+ tc.handshake()
+ tc.ignoreFrame(frameTypeAck)
+ //tc.ignoreFrame(frameTypeRetireConnectionID)
+
+ // Send CID 2, retire 0-1 (negotiated during the handshake).
+ tc.writeFrames(packetType1RTT,
+ debugFrameNewConnectionID{
+ seq: 2,
+ retirePriorTo: 2,
+ connID: testPeerConnID(2),
+ token: testPeerStatelessResetToken(2),
+ })
+ tc.wantFrame("retire CID 0", packetType1RTT, debugFrameRetireConnectionID{seq: 0})
+ tc.wantFrame("retire CID 1", packetType1RTT, debugFrameRetireConnectionID{seq: 1})
+
+ // Send CID 3, retire 2.
+ tc.writeFrames(packetType1RTT,
+ debugFrameNewConnectionID{
+ seq: 3,
+ retirePriorTo: 3,
+ connID: testPeerConnID(3),
+ token: testPeerStatelessResetToken(3),
+ })
+ tc.wantFrame("retire CID 2", packetType1RTT, debugFrameRetireConnectionID{seq: 2})
+
+ // Acknowledge retirement of CIDs 0-2.
+ // The server should have state for only one CID: 3.
+ tc.writeAckForAll()
+ if got, want := len(tc.conn.connIDState.remote), 1; got != want {
+ t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
+ }
+
+ // Send CID 2 again.
+ // The server should ignore this, since it's already retired the CID.
+ tc.ignoreFrames[frameTypeRetireConnectionID] = false
+ tc.writeFrames(packetType1RTT,
+ debugFrameNewConnectionID{
+ seq: 2,
+ connID: testPeerConnID(2),
+ token: testPeerStatelessResetToken(2),
+ })
+ if got, want := len(tc.conn.connIDState.remote), 1; got != want {
+ t.Fatalf("connection has state for %v connection IDs, want %v", got, want)
+ }
+ tc.wantIdle("server does not re-retire already retired CID 2")
+}
diff --git a/quic/conn_test.go b/quic/conn_test.go
index f4f1818a64..51402630fc 100644
--- a/quic/conn_test.go
+++ b/quic/conn_test.go
@@ -436,7 +436,7 @@ func (tc *testConn) write(d *testDatagram) {
tc.endpoint.writeDatagram(d)
}
-// writeFrame sends the Conn a datagram containing the given frames.
+// writeFrames sends the Conn a datagram containing the given frames.
func (tc *testConn) writeFrames(ptype packetType, frames ...debugFrame) {
tc.t.Helper()
space := spaceForPacketType(ptype)
diff --git a/quic/endpoint.go b/quic/endpoint.go
index a55336b240..b9ababe6b1 100644
--- a/quic/endpoint.go
+++ b/quic/endpoint.go
@@ -73,6 +73,25 @@ func Listen(network, address string, listenConfig *Config) (*Endpoint, error) {
return newEndpoint(pc, listenConfig, nil)
}
+// NewEndpoint creates an endpoint using a net.PacketConn as the underlying transport.
+//
+// If the PacketConn is not a *net.UDPConn, the endpoint may be slower and lack
+// access to some features of the network.
+func NewEndpoint(conn net.PacketConn, config *Config) (*Endpoint, error) {
+ var pc packetConn
+ var err error
+ switch conn := conn.(type) {
+ case *net.UDPConn:
+ pc, err = newNetUDPConn(conn)
+ default:
+ pc, err = newNetPacketConn(conn)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return newEndpoint(pc, config, nil)
+}
+
func newEndpoint(pc packetConn, config *Config, hooks endpointTestHooks) (*Endpoint, error) {
e := &Endpoint{
listenConfig: config,
@@ -448,7 +467,7 @@ func (m *connsMap) updateConnIDs(f func(*connsMap)) {
m.updateNeeded.Store(true)
}
-// applyConnIDUpdates is called by the datagram receive loop to update its connection ID map.
+// applyUpdates is called by the datagram receive loop to update its connection ID map.
func (m *connsMap) applyUpdates() {
m.updateMu.Lock()
defer m.updateMu.Unlock()
diff --git a/quic/errors.go b/quic/errors.go
index 954793cfc0..b805b93c1b 100644
--- a/quic/errors.go
+++ b/quic/errors.go
@@ -121,8 +121,7 @@ type ApplicationError struct {
}
func (e *ApplicationError) Error() string {
- // TODO: Include the Reason string here, but sanitize it first.
- return fmt.Sprintf("AppError %v", e.Code)
+ return fmt.Sprintf("peer closed connection: %v: %q", e.Code, e.Reason)
}
// Is reports a match if err is an *ApplicationError with a matching Code.
diff --git a/quic/gate.go b/quic/gate.go
index a2fb537115..8f1db2be66 100644
--- a/quic/gate.go
+++ b/quic/gate.go
@@ -27,7 +27,7 @@ func newGate() gate {
return g
}
-// newLocked gate returns a new, locked gate.
+// newLockedGate returns a new, locked gate.
func newLockedGate() gate {
return gate{
set: make(chan struct{}, 1),
@@ -84,7 +84,7 @@ func (g *gate) unlock(set bool) {
}
}
-// unlock sets the condition to the result of f and releases the gate.
+// unlockFunc sets the condition to the result of f and releases the gate.
// Useful in defers.
func (g *gate) unlockFunc(f func() bool) {
g.unlock(f())
diff --git a/quic/packet.go b/quic/packet.go
index 7a874319d7..883754f021 100644
--- a/quic/packet.go
+++ b/quic/packet.go
@@ -9,6 +9,8 @@ package quic
import (
"encoding/binary"
"fmt"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// packetType is a QUIC packet type.
@@ -196,10 +198,10 @@ func parseVersionNegotiation(pkt []byte) (dstConnID, srcConnID, versions []byte)
// appendVersionNegotiation appends a Version Negotiation packet to pkt,
// returning the result.
func appendVersionNegotiation(pkt, dstConnID, srcConnID []byte, versions ...uint32) []byte {
- pkt = append(pkt, headerFormLong|fixedBit) // header byte
- pkt = append(pkt, 0, 0, 0, 0) // Version (0 for Version Negotiation)
- pkt = appendUint8Bytes(pkt, dstConnID) // Destination Connection ID
- pkt = appendUint8Bytes(pkt, srcConnID) // Source Connection ID
+ pkt = append(pkt, headerFormLong|fixedBit) // header byte
+ pkt = append(pkt, 0, 0, 0, 0) // Version (0 for Version Negotiation)
+ pkt = quicwire.AppendUint8Bytes(pkt, dstConnID) // Destination Connection ID
+ pkt = quicwire.AppendUint8Bytes(pkt, srcConnID) // Source Connection ID
for _, v := range versions {
pkt = binary.BigEndian.AppendUint32(pkt, v) // Supported Version
}
@@ -243,21 +245,21 @@ func parseGenericLongHeaderPacket(b []byte) (p genericLongPacket, ok bool) {
b = b[1:]
// Version (32),
var n int
- p.version, n = consumeUint32(b)
+ p.version, n = quicwire.ConsumeUint32(b)
if n < 0 {
return genericLongPacket{}, false
}
b = b[n:]
// Destination Connection ID Length (8),
// Destination Connection ID (0..2048),
- p.dstConnID, n = consumeUint8Bytes(b)
+ p.dstConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > 2048/8 {
return genericLongPacket{}, false
}
b = b[n:]
// Source Connection ID Length (8),
// Source Connection ID (0..2048),
- p.srcConnID, n = consumeUint8Bytes(b)
+ p.srcConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > 2048/8 {
return genericLongPacket{}, false
}
diff --git a/quic/packet_codec_test.go b/quic/packet_codec_test.go
index 3b39795ef5..2a2b08f4e3 100644
--- a/quic/packet_codec_test.go
+++ b/quic/packet_codec_test.go
@@ -15,6 +15,7 @@ import (
"testing"
"time"
+ "golang.org/x/net/internal/quic/quicwire"
"golang.org/x/net/quic/qlog"
)
@@ -736,7 +737,7 @@ func TestFrameDecodeErrors(t *testing.T) {
name: "MAX_STREAMS with too many streams",
b: func() []byte {
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.11-5.2.1
- return appendVarint([]byte{frameTypeMaxStreamsBidi}, (1<<60)+1)
+ return quicwire.AppendVarint([]byte{frameTypeMaxStreamsBidi}, (1<<60)+1)
}(),
}, {
name: "NEW_CONNECTION_ID too small",
diff --git a/quic/packet_parser.go b/quic/packet_parser.go
index feef9eac7f..dca3018086 100644
--- a/quic/packet_parser.go
+++ b/quic/packet_parser.go
@@ -6,6 +6,8 @@
package quic
+import "golang.org/x/net/internal/quic/quicwire"
+
// parseLongHeaderPacket parses a QUIC long header packet.
//
// It does not parse Version Negotiation packets.
@@ -34,7 +36,7 @@ func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p lon
}
b = b[1:]
// Version (32),
- p.version, n = consumeUint32(b)
+ p.version, n = quicwire.ConsumeUint32(b)
if n < 0 {
return longPacket{}, -1
}
@@ -46,7 +48,7 @@ func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p lon
// Destination Connection ID Length (8),
// Destination Connection ID (0..160),
- p.dstConnID, n = consumeUint8Bytes(b)
+ p.dstConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > maxConnIDLen {
return longPacket{}, -1
}
@@ -54,7 +56,7 @@ func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p lon
// Source Connection ID Length (8),
// Source Connection ID (0..160),
- p.srcConnID, n = consumeUint8Bytes(b)
+ p.srcConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > maxConnIDLen {
return longPacket{}, -1
}
@@ -64,7 +66,7 @@ func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p lon
case packetTypeInitial:
// Token Length (i),
// Token (..),
- p.extra, n = consumeVarintBytes(b)
+ p.extra, n = quicwire.ConsumeVarintBytes(b)
if n < 0 {
return longPacket{}, -1
}
@@ -77,7 +79,7 @@ func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p lon
}
// Length (i),
- payLen, n := consumeVarint(b)
+ payLen, n := quicwire.ConsumeVarint(b)
if n < 0 {
return longPacket{}, -1
}
@@ -121,14 +123,14 @@ func skipLongHeaderPacket(pkt []byte) int {
}
if getPacketType(pkt) == packetTypeInitial {
// Token length, token.
- _, nn := consumeVarintBytes(pkt[n:])
+ _, nn := quicwire.ConsumeVarintBytes(pkt[n:])
if nn < 0 {
return -1
}
n += nn
}
// Length, packet number, payload.
- _, nn := consumeVarintBytes(pkt[n:])
+ _, nn := quicwire.ConsumeVarintBytes(pkt[n:])
if nn < 0 {
return -1
}
@@ -160,20 +162,20 @@ func parse1RTTPacket(pkt []byte, k *updatingKeyPair, dstConnIDLen int, pnumMax p
func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumber)) (largest packetNumber, ackDelay unscaledAckDelay, n int) {
b := frame[1:] // type
- largestAck, n := consumeVarint(b)
+ largestAck, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
b = b[n:]
- v, n := consumeVarintInt64(b)
+ v, n := quicwire.ConsumeVarintInt64(b)
if n < 0 {
return 0, 0, -1
}
b = b[n:]
ackDelay = unscaledAckDelay(v)
- ackRangeCount, n := consumeVarint(b)
+ ackRangeCount, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
@@ -181,7 +183,7 @@ func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumbe
rangeMax := packetNumber(largestAck)
for i := uint64(0); ; i++ {
- rangeLen, n := consumeVarint(b)
+ rangeLen, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
@@ -196,7 +198,7 @@ func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumbe
break
}
- gap, n := consumeVarint(b)
+ gap, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
@@ -209,17 +211,17 @@ func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumbe
return packetNumber(largestAck), ackDelay, len(frame) - len(b)
}
- ect0Count, n := consumeVarint(b)
+ ect0Count, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
b = b[n:]
- ect1Count, n := consumeVarint(b)
+ ect1Count, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
b = b[n:]
- ecnCECount, n := consumeVarint(b)
+ ecnCECount, n := quicwire.ConsumeVarint(b)
if n < 0 {
return 0, 0, -1
}
@@ -236,17 +238,17 @@ func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumbe
func consumeResetStreamFrame(b []byte) (id streamID, code uint64, finalSize int64, n int) {
n = 1
- idInt, nn := consumeVarint(b[n:])
+ idInt, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, 0, -1
}
n += nn
- code, nn = consumeVarint(b[n:])
+ code, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, 0, -1
}
n += nn
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, 0, -1
}
@@ -257,12 +259,12 @@ func consumeResetStreamFrame(b []byte) (id streamID, code uint64, finalSize int6
func consumeStopSendingFrame(b []byte) (id streamID, code uint64, n int) {
n = 1
- idInt, nn := consumeVarint(b[n:])
+ idInt, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
n += nn
- code, nn = consumeVarint(b[n:])
+ code, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
@@ -272,13 +274,13 @@ func consumeStopSendingFrame(b []byte) (id streamID, code uint64, n int) {
func consumeCryptoFrame(b []byte) (off int64, data []byte, n int) {
n = 1
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, nil, -1
}
off = int64(v)
n += nn
- data, nn = consumeVarintBytes(b[n:])
+ data, nn = quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return 0, nil, -1
}
@@ -288,7 +290,7 @@ func consumeCryptoFrame(b []byte) (off int64, data []byte, n int) {
func consumeNewTokenFrame(b []byte) (token []byte, n int) {
n = 1
- data, nn := consumeVarintBytes(b[n:])
+ data, nn := quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return nil, -1
}
@@ -302,13 +304,13 @@ func consumeNewTokenFrame(b []byte) (token []byte, n int) {
func consumeStreamFrame(b []byte) (id streamID, off int64, fin bool, data []byte, n int) {
fin = (b[0] & 0x01) != 0
n = 1
- idInt, nn := consumeVarint(b[n:])
+ idInt, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, false, nil, -1
}
n += nn
if b[0]&0x04 != 0 {
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, false, nil, -1
}
@@ -316,7 +318,7 @@ func consumeStreamFrame(b []byte) (id streamID, off int64, fin bool, data []byte
off = int64(v)
}
if b[0]&0x02 != 0 {
- data, nn = consumeVarintBytes(b[n:])
+ data, nn = quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return 0, 0, false, nil, -1
}
@@ -333,7 +335,7 @@ func consumeStreamFrame(b []byte) (id streamID, off int64, fin bool, data []byte
func consumeMaxDataFrame(b []byte) (max int64, n int) {
n = 1
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, -1
}
@@ -343,13 +345,13 @@ func consumeMaxDataFrame(b []byte) (max int64, n int) {
func consumeMaxStreamDataFrame(b []byte) (id streamID, max int64, n int) {
n = 1
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
n += nn
id = streamID(v)
- v, nn = consumeVarint(b[n:])
+ v, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
@@ -368,7 +370,7 @@ func consumeMaxStreamsFrame(b []byte) (typ streamType, max int64, n int) {
return 0, 0, -1
}
n = 1
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
@@ -381,13 +383,13 @@ func consumeMaxStreamsFrame(b []byte) (typ streamType, max int64, n int) {
func consumeStreamDataBlockedFrame(b []byte) (id streamID, max int64, n int) {
n = 1
- v, nn := consumeVarint(b[n:])
+ v, nn := quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, -1
}
n += nn
id = streamID(v)
- max, nn = consumeVarintInt64(b[n:])
+ max, nn = quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, 0, -1
}
@@ -397,7 +399,7 @@ func consumeStreamDataBlockedFrame(b []byte) (id streamID, max int64, n int) {
func consumeDataBlockedFrame(b []byte) (max int64, n int) {
n = 1
- max, nn := consumeVarintInt64(b[n:])
+ max, nn := quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, -1
}
@@ -412,7 +414,7 @@ func consumeStreamsBlockedFrame(b []byte) (typ streamType, max int64, n int) {
typ = uniStream
}
n = 1
- max, nn := consumeVarintInt64(b[n:])
+ max, nn := quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, 0, -1
}
@@ -423,12 +425,12 @@ func consumeStreamsBlockedFrame(b []byte) (typ streamType, max int64, n int) {
func consumeNewConnectionIDFrame(b []byte) (seq, retire int64, connID []byte, resetToken statelessResetToken, n int) {
n = 1
var nn int
- seq, nn = consumeVarintInt64(b[n:])
+ seq, nn = quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, 0, nil, statelessResetToken{}, -1
}
n += nn
- retire, nn = consumeVarintInt64(b[n:])
+ retire, nn = quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, 0, nil, statelessResetToken{}, -1
}
@@ -436,7 +438,7 @@ func consumeNewConnectionIDFrame(b []byte) (seq, retire int64, connID []byte, re
if seq < retire {
return 0, 0, nil, statelessResetToken{}, -1
}
- connID, nn = consumeVarintBytes(b[n:])
+ connID, nn = quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return 0, 0, nil, statelessResetToken{}, -1
}
@@ -455,7 +457,7 @@ func consumeNewConnectionIDFrame(b []byte) (seq, retire int64, connID []byte, re
func consumeRetireConnectionIDFrame(b []byte) (seq int64, n int) {
n = 1
var nn int
- seq, nn = consumeVarintInt64(b[n:])
+ seq, nn = quicwire.ConsumeVarintInt64(b[n:])
if nn < 0 {
return 0, -1
}
@@ -481,18 +483,18 @@ func consumeConnectionCloseTransportFrame(b []byte) (code transportError, frameT
n = 1
var nn int
var codeInt uint64
- codeInt, nn = consumeVarint(b[n:])
+ codeInt, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, "", -1
}
code = transportError(codeInt)
n += nn
- frameType, nn = consumeVarint(b[n:])
+ frameType, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, 0, "", -1
}
n += nn
- reasonb, nn := consumeVarintBytes(b[n:])
+ reasonb, nn := quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return 0, 0, "", -1
}
@@ -504,12 +506,12 @@ func consumeConnectionCloseTransportFrame(b []byte) (code transportError, frameT
func consumeConnectionCloseApplicationFrame(b []byte) (code uint64, reason string, n int) {
n = 1
var nn int
- code, nn = consumeVarint(b[n:])
+ code, nn = quicwire.ConsumeVarint(b[n:])
if nn < 0 {
return 0, "", -1
}
n += nn
- reasonb, nn := consumeVarintBytes(b[n:])
+ reasonb, nn := quicwire.ConsumeVarintBytes(b[n:])
if nn < 0 {
return 0, "", -1
}
diff --git a/quic/packet_protection.go b/quic/packet_protection.go
index fe48c14c5d..9f1bbc6a4a 100644
--- a/quic/packet_protection.go
+++ b/quic/packet_protection.go
@@ -519,7 +519,7 @@ func hashForSuite(suite uint16) (h crypto.Hash, keySize int) {
}
}
-// hdkfExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
+// hkdfExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
//
// Copied from crypto/tls/key_schedule.go.
func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte {
diff --git a/quic/packet_writer.go b/quic/packet_writer.go
index e4d71e622b..e75edcda5b 100644
--- a/quic/packet_writer.go
+++ b/quic/packet_writer.go
@@ -8,6 +8,8 @@ package quic
import (
"encoding/binary"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// A packetWriter constructs QUIC datagrams.
@@ -47,7 +49,7 @@ func (w *packetWriter) datagram() []byte {
return w.b
}
-// packet returns the size of the current packet.
+// packetLen returns the size of the current packet.
func (w *packetWriter) packetLen() int {
return len(w.b[w.pktOff:]) + aeadOverhead
}
@@ -74,7 +76,7 @@ func (w *packetWriter) startProtectedLongHeaderPacket(pnumMaxAcked packetNumber,
hdrSize += 1 + len(p.srcConnID)
switch p.ptype {
case packetTypeInitial:
- hdrSize += sizeVarint(uint64(len(p.extra))) + len(p.extra)
+ hdrSize += quicwire.SizeVarint(uint64(len(p.extra))) + len(p.extra)
}
hdrSize += 2 // length, hardcoded to a 2-byte varint
pnumOff := len(w.b) + hdrSize
@@ -127,11 +129,11 @@ func (w *packetWriter) finishProtectedLongHeaderPacket(pnumMaxAcked packetNumber
}
hdr = append(hdr, headerFormLong|fixedBit|typeBits|byte(pnumLen-1))
hdr = binary.BigEndian.AppendUint32(hdr, p.version)
- hdr = appendUint8Bytes(hdr, p.dstConnID)
- hdr = appendUint8Bytes(hdr, p.srcConnID)
+ hdr = quicwire.AppendUint8Bytes(hdr, p.dstConnID)
+ hdr = quicwire.AppendUint8Bytes(hdr, p.srcConnID)
switch p.ptype {
case packetTypeInitial:
- hdr = appendVarintBytes(hdr, p.extra) // token
+ hdr = quicwire.AppendVarintBytes(hdr, p.extra) // token
}
// Packet length, always encoded as a 2-byte varint.
@@ -270,26 +272,26 @@ func (w *packetWriter) appendAckFrame(seen rangeset[packetNumber], delay unscale
largest = uint64(seen.max())
firstRange = uint64(seen[len(seen)-1].size() - 1)
)
- if w.avail() < 1+sizeVarint(largest)+sizeVarint(uint64(delay))+1+sizeVarint(firstRange) {
+ if w.avail() < 1+quicwire.SizeVarint(largest)+quicwire.SizeVarint(uint64(delay))+1+quicwire.SizeVarint(firstRange) {
return false
}
w.b = append(w.b, frameTypeAck)
- w.b = appendVarint(w.b, largest)
- w.b = appendVarint(w.b, uint64(delay))
+ w.b = quicwire.AppendVarint(w.b, largest)
+ w.b = quicwire.AppendVarint(w.b, uint64(delay))
// The range count is technically a varint, but we'll reserve a single byte for it
// and never add more than 62 ranges (the maximum varint that fits in a byte).
rangeCountOff := len(w.b)
w.b = append(w.b, 0)
- w.b = appendVarint(w.b, firstRange)
+ w.b = quicwire.AppendVarint(w.b, firstRange)
rangeCount := byte(0)
for i := len(seen) - 2; i >= 0; i-- {
gap := uint64(seen[i+1].start - seen[i].end - 1)
size := uint64(seen[i].size() - 1)
- if w.avail() < sizeVarint(gap)+sizeVarint(size) || rangeCount > 62 {
+ if w.avail() < quicwire.SizeVarint(gap)+quicwire.SizeVarint(size) || rangeCount > 62 {
break
}
- w.b = appendVarint(w.b, gap)
- w.b = appendVarint(w.b, size)
+ w.b = quicwire.AppendVarint(w.b, gap)
+ w.b = quicwire.AppendVarint(w.b, size)
rangeCount++
}
w.b[rangeCountOff] = rangeCount
@@ -299,34 +301,34 @@ func (w *packetWriter) appendAckFrame(seen rangeset[packetNumber], delay unscale
}
func (w *packetWriter) appendNewTokenFrame(token []byte) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(len(token)))+len(token) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(len(token)))+len(token) {
return false
}
w.b = append(w.b, frameTypeNewToken)
- w.b = appendVarintBytes(w.b, token)
+ w.b = quicwire.AppendVarintBytes(w.b, token)
return true
}
func (w *packetWriter) appendResetStreamFrame(id streamID, code uint64, finalSize int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code)+sizeVarint(uint64(finalSize)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(code)+quicwire.SizeVarint(uint64(finalSize)) {
return false
}
w.b = append(w.b, frameTypeResetStream)
- w.b = appendVarint(w.b, uint64(id))
- w.b = appendVarint(w.b, code)
- w.b = appendVarint(w.b, uint64(finalSize))
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, code)
+ w.b = quicwire.AppendVarint(w.b, uint64(finalSize))
w.sent.appendAckElicitingFrame(frameTypeResetStream)
w.sent.appendInt(uint64(id))
return true
}
func (w *packetWriter) appendStopSendingFrame(id streamID, code uint64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(code) {
return false
}
w.b = append(w.b, frameTypeStopSending)
- w.b = appendVarint(w.b, uint64(id))
- w.b = appendVarint(w.b, code)
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, code)
w.sent.appendAckElicitingFrame(frameTypeStopSending)
w.sent.appendInt(uint64(id))
return true
@@ -337,9 +339,9 @@ func (w *packetWriter) appendStopSendingFrame(id streamID, code uint64) (added b
// The returned []byte may be smaller than size if the packet cannot hold all the data.
func (w *packetWriter) appendCryptoFrame(off int64, size int) (_ []byte, added bool) {
max := w.avail()
- max -= 1 // frame type
- max -= sizeVarint(uint64(off)) // offset
- max -= sizeVarint(uint64(size)) // maximum length
+ max -= 1 // frame type
+ max -= quicwire.SizeVarint(uint64(off)) // offset
+ max -= quicwire.SizeVarint(uint64(size)) // maximum length
if max <= 0 {
return nil, false
}
@@ -347,8 +349,8 @@ func (w *packetWriter) appendCryptoFrame(off int64, size int) (_ []byte, added b
size = max
}
w.b = append(w.b, frameTypeCrypto)
- w.b = appendVarint(w.b, uint64(off))
- w.b = appendVarint(w.b, uint64(size))
+ w.b = quicwire.AppendVarint(w.b, uint64(off))
+ w.b = quicwire.AppendVarint(w.b, uint64(size))
start := len(w.b)
w.b = w.b[:start+size]
w.sent.appendAckElicitingFrame(frameTypeCrypto)
@@ -363,12 +365,12 @@ func (w *packetWriter) appendStreamFrame(id streamID, off int64, size int, fin b
typ := uint8(frameTypeStreamBase | streamLenBit)
max := w.avail()
max -= 1 // frame type
- max -= sizeVarint(uint64(id))
+ max -= quicwire.SizeVarint(uint64(id))
if off != 0 {
- max -= sizeVarint(uint64(off))
+ max -= quicwire.SizeVarint(uint64(off))
typ |= streamOffBit
}
- max -= sizeVarint(uint64(size)) // maximum length
+ max -= quicwire.SizeVarint(uint64(size)) // maximum length
if max < 0 || (max == 0 && size > 0) {
return nil, false
}
@@ -378,11 +380,11 @@ func (w *packetWriter) appendStreamFrame(id streamID, off int64, size int, fin b
typ |= streamFinBit
}
w.b = append(w.b, typ)
- w.b = appendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
if off != 0 {
- w.b = appendVarint(w.b, uint64(off))
+ w.b = quicwire.AppendVarint(w.b, uint64(off))
}
- w.b = appendVarint(w.b, uint64(size))
+ w.b = quicwire.AppendVarint(w.b, uint64(size))
start := len(w.b)
w.b = w.b[:start+size]
w.sent.appendAckElicitingFrame(typ & (frameTypeStreamBase | streamFinBit))
@@ -392,29 +394,29 @@ func (w *packetWriter) appendStreamFrame(id streamID, off int64, size int, fin b
}
func (w *packetWriter) appendMaxDataFrame(max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(max)) {
return false
}
w.b = append(w.b, frameTypeMaxData)
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(frameTypeMaxData)
return true
}
func (w *packetWriter) appendMaxStreamDataFrame(id streamID, max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(uint64(max)) {
return false
}
w.b = append(w.b, frameTypeMaxStreamData)
- w.b = appendVarint(w.b, uint64(id))
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(frameTypeMaxStreamData)
w.sent.appendInt(uint64(id))
return true
}
func (w *packetWriter) appendMaxStreamsFrame(streamType streamType, max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(max)) {
return false
}
var typ byte
@@ -424,35 +426,35 @@ func (w *packetWriter) appendMaxStreamsFrame(streamType streamType, max int64) (
typ = frameTypeMaxStreamsUni
}
w.b = append(w.b, typ)
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(typ)
return true
}
func (w *packetWriter) appendDataBlockedFrame(max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(max)) {
return false
}
w.b = append(w.b, frameTypeDataBlocked)
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(frameTypeDataBlocked)
return true
}
func (w *packetWriter) appendStreamDataBlockedFrame(id streamID, max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(uint64(max)) {
return false
}
w.b = append(w.b, frameTypeStreamDataBlocked)
- w.b = appendVarint(w.b, uint64(id))
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(frameTypeStreamDataBlocked)
w.sent.appendInt(uint64(id))
return true
}
func (w *packetWriter) appendStreamsBlockedFrame(typ streamType, max int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(max)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(max)) {
return false
}
var ftype byte
@@ -462,19 +464,19 @@ func (w *packetWriter) appendStreamsBlockedFrame(typ streamType, max int64) (add
ftype = frameTypeStreamsBlockedUni
}
w.b = append(w.b, ftype)
- w.b = appendVarint(w.b, uint64(max))
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
w.sent.appendAckElicitingFrame(ftype)
return true
}
func (w *packetWriter) appendNewConnectionIDFrame(seq, retirePriorTo int64, connID []byte, token [16]byte) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(seq))+sizeVarint(uint64(retirePriorTo))+1+len(connID)+len(token) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(seq))+quicwire.SizeVarint(uint64(retirePriorTo))+1+len(connID)+len(token) {
return false
}
w.b = append(w.b, frameTypeNewConnectionID)
- w.b = appendVarint(w.b, uint64(seq))
- w.b = appendVarint(w.b, uint64(retirePriorTo))
- w.b = appendUint8Bytes(w.b, connID)
+ w.b = quicwire.AppendVarint(w.b, uint64(seq))
+ w.b = quicwire.AppendVarint(w.b, uint64(retirePriorTo))
+ w.b = quicwire.AppendUint8Bytes(w.b, connID)
w.b = append(w.b, token[:]...)
w.sent.appendAckElicitingFrame(frameTypeNewConnectionID)
w.sent.appendInt(uint64(seq))
@@ -482,11 +484,11 @@ func (w *packetWriter) appendNewConnectionIDFrame(seq, retirePriorTo int64, conn
}
func (w *packetWriter) appendRetireConnectionIDFrame(seq int64) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(seq)) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(seq)) {
return false
}
w.b = append(w.b, frameTypeRetireConnectionID)
- w.b = appendVarint(w.b, uint64(seq))
+ w.b = quicwire.AppendVarint(w.b, uint64(seq))
w.sent.appendAckElicitingFrame(frameTypeRetireConnectionID)
w.sent.appendInt(uint64(seq))
return true
@@ -515,27 +517,27 @@ func (w *packetWriter) appendPathResponseFrame(data pathChallengeData) (added bo
// appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame
// carrying a transport error code.
func (w *packetWriter) appendConnectionCloseTransportFrame(code transportError, frameType uint64, reason string) (added bool) {
- if w.avail() < 1+sizeVarint(uint64(code))+sizeVarint(frameType)+sizeVarint(uint64(len(reason)))+len(reason) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(code))+quicwire.SizeVarint(frameType)+quicwire.SizeVarint(uint64(len(reason)))+len(reason) {
return false
}
w.b = append(w.b, frameTypeConnectionCloseTransport)
- w.b = appendVarint(w.b, uint64(code))
- w.b = appendVarint(w.b, frameType)
- w.b = appendVarintBytes(w.b, []byte(reason))
+ w.b = quicwire.AppendVarint(w.b, uint64(code))
+ w.b = quicwire.AppendVarint(w.b, frameType)
+ w.b = quicwire.AppendVarintBytes(w.b, []byte(reason))
// We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or
// detected as lost.
return true
}
-// appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame
+// appendConnectionCloseApplicationFrame appends a CONNECTION_CLOSE frame
// carrying an application protocol error code.
func (w *packetWriter) appendConnectionCloseApplicationFrame(code uint64, reason string) (added bool) {
- if w.avail() < 1+sizeVarint(code)+sizeVarint(uint64(len(reason)))+len(reason) {
+ if w.avail() < 1+quicwire.SizeVarint(code)+quicwire.SizeVarint(uint64(len(reason)))+len(reason) {
return false
}
w.b = append(w.b, frameTypeConnectionCloseApplication)
- w.b = appendVarint(w.b, code)
- w.b = appendVarintBytes(w.b, []byte(reason))
+ w.b = quicwire.AppendVarint(w.b, code)
+ w.b = quicwire.AppendVarintBytes(w.b, []byte(reason))
// We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or
// detected as lost.
return true
diff --git a/quic/qlog/json_writer.go b/quic/qlog/json_writer.go
index 6fb8d33b25..7867c590df 100644
--- a/quic/qlog/json_writer.go
+++ b/quic/qlog/json_writer.go
@@ -58,7 +58,7 @@ func (w *jsonWriter) writeAttr(a slog.Attr) {
w.writeValue(a.Value)
}
-// writeAttr writes a []slog.Attr as an object field.
+// writeAttrsField writes a []slog.Attr as an object field.
func (w *jsonWriter) writeAttrsField(name string, attrs []slog.Attr) {
w.writeName(name)
w.writeAttrs(attrs)
@@ -113,7 +113,7 @@ func (w *jsonWriter) writeObject(f func()) {
w.buf.WriteByte('}')
}
-// writeObject writes an object-valued object field.
+// writeObjectField writes an object-valued object field.
// The function f is called to write the contents.
func (w *jsonWriter) writeObjectField(name string, f func()) {
w.writeName(name)
diff --git a/quic/rangeset.go b/quic/rangeset.go
index b8b2e93672..528d53df39 100644
--- a/quic/rangeset.go
+++ b/quic/rangeset.go
@@ -159,6 +159,14 @@ func (s rangeset[T]) numRanges() int {
return len(s)
}
+// size returns the size of all ranges in the rangeset.
+func (s rangeset[T]) size() (total T) {
+ for _, r := range s {
+ total += r.size()
+ }
+ return total
+}
+
// isrange reports if the rangeset covers exactly the range [start, end).
func (s rangeset[T]) isrange(start, end T) bool {
switch len(s) {
diff --git a/quic/retry.go b/quic/retry.go
index 5dc39d1d9d..8c56ee1b10 100644
--- a/quic/retry.go
+++ b/quic/retry.go
@@ -16,6 +16,7 @@ import (
"time"
"golang.org/x/crypto/chacha20poly1305"
+ "golang.org/x/net/internal/quic/quicwire"
)
// AEAD and nonce used to compute the Retry Integrity Tag.
@@ -133,7 +134,7 @@ func (rs *retryState) validateToken(now time.Time, token, srcConnID, dstConnID [
func (rs *retryState) additionalData(srcConnID []byte, addr netip.AddrPort) []byte {
var additional []byte
- additional = appendUint8Bytes(additional, srcConnID)
+ additional = quicwire.AppendUint8Bytes(additional, srcConnID)
additional = append(additional, addr.Addr().AsSlice()...)
additional = binary.BigEndian.AppendUint16(additional, addr.Port())
return additional
@@ -141,7 +142,7 @@ func (rs *retryState) additionalData(srcConnID []byte, addr netip.AddrPort) []by
func (e *Endpoint) validateInitialAddress(now time.Time, p genericLongPacket, peerAddr netip.AddrPort) (origDstConnID []byte, ok bool) {
// The retry token is at the start of an Initial packet's data.
- token, n := consumeUint8Bytes(p.data)
+ token, n := quicwire.ConsumeUint8Bytes(p.data)
if n < 0 {
// We've already validated that the packet is at least 1200 bytes long,
// so there's no way for even a maximum size token to not fit.
@@ -196,12 +197,12 @@ func encodeRetryPacket(originalDstConnID []byte, p retryPacket) []byte {
// Create the pseudo-packet (including the original DCID), append the tag,
// and return the Retry packet.
var b []byte
- b = appendUint8Bytes(b, originalDstConnID) // Original Destination Connection ID
- start := len(b) // start of the Retry packet
+ b = quicwire.AppendUint8Bytes(b, originalDstConnID) // Original Destination Connection ID
+ start := len(b) // start of the Retry packet
b = append(b, headerFormLong|fixedBit|longPacketTypeRetry)
b = binary.BigEndian.AppendUint32(b, quicVersion1) // Version
- b = appendUint8Bytes(b, p.dstConnID) // Destination Connection ID
- b = appendUint8Bytes(b, p.srcConnID) // Source Connection ID
+ b = quicwire.AppendUint8Bytes(b, p.dstConnID) // Destination Connection ID
+ b = quicwire.AppendUint8Bytes(b, p.srcConnID) // Source Connection ID
b = append(b, p.token...) // Token
b = retryAEAD.Seal(b, retryNonce, nil, b) // Retry Integrity Tag
return b[start:]
@@ -222,7 +223,7 @@ func parseRetryPacket(b, origDstConnID []byte) (p retryPacket, ok bool) {
// Create the pseudo-packet consisting of the original destination connection ID
// followed by the Retry packet (less the integrity tag).
// Use this to validate the packet integrity tag.
- pseudo := appendUint8Bytes(nil, origDstConnID)
+ pseudo := quicwire.AppendUint8Bytes(nil, origDstConnID)
pseudo = append(pseudo, b[:len(b)-retryIntegrityTagLength]...)
wantTag := retryAEAD.Seal(nil, retryNonce, nil, pseudo)
if !bytes.Equal(gotTag, wantTag) {
diff --git a/quic/rtt.go b/quic/rtt.go
index 4942f8cca1..494060c67d 100644
--- a/quic/rtt.go
+++ b/quic/rtt.go
@@ -37,7 +37,7 @@ func (r *rttState) establishPersistentCongestion() {
r.minRTT = r.latestRTT
}
-// updateRTTSample is called when we generate a new RTT sample.
+// updateSample is called when we generate a new RTT sample.
// https://www.rfc-editor.org/rfc/rfc9002.html#section-5
func (r *rttState) updateSample(now time.Time, handshakeConfirmed bool, spaceID numberSpace, latestRTT, ackDelay, maxAckDelay time.Duration) {
r.latestRTT = latestRTT
diff --git a/quic/sent_packet.go b/quic/sent_packet.go
index 226152327d..eedd2f61b3 100644
--- a/quic/sent_packet.go
+++ b/quic/sent_packet.go
@@ -9,6 +9,8 @@ package quic
import (
"sync"
"time"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// A sentPacket tracks state related to an in-flight packet we sent,
@@ -78,12 +80,12 @@ func (sent *sentPacket) appendAckElicitingFrame(frameType byte) {
}
func (sent *sentPacket) appendInt(v uint64) {
- sent.b = appendVarint(sent.b, v)
+ sent.b = quicwire.AppendVarint(sent.b, v)
}
func (sent *sentPacket) appendOffAndSize(start int64, size int) {
- sent.b = appendVarint(sent.b, uint64(start))
- sent.b = appendVarint(sent.b, uint64(size))
+ sent.b = quicwire.AppendVarint(sent.b, uint64(start))
+ sent.b = quicwire.AppendVarint(sent.b, uint64(size))
}
// The next* methods read back information about frames in the packet.
@@ -95,7 +97,7 @@ func (sent *sentPacket) next() (frameType byte) {
}
func (sent *sentPacket) nextInt() uint64 {
- v, n := consumeVarint(sent.b[sent.n:])
+ v, n := quicwire.ConsumeVarint(sent.b[sent.n:])
sent.n += n
return v
}
diff --git a/quic/sent_val.go b/quic/sent_val.go
index 31f69e47d0..920658919b 100644
--- a/quic/sent_val.go
+++ b/quic/sent_val.go
@@ -37,7 +37,7 @@ func (s sentVal) isSet() bool { return s != 0 }
// shouldSend reports whether the value is set and has not been sent to the peer.
func (s sentVal) shouldSend() bool { return s.state() == sentValUnsent }
-// shouldSend reports whether the value needs to be sent to the peer.
+// shouldSendPTO reports whether the value needs to be sent to the peer.
// The value needs to be sent if it is set and has not been sent.
// If pto is true, indicating that we are sending a PTO probe, the value
// should also be sent if it is set and has not been acknowledged.
diff --git a/quic/stream.go b/quic/stream.go
index cb45534f82..8068b10acd 100644
--- a/quic/stream.go
+++ b/quic/stream.go
@@ -12,6 +12,8 @@ import (
"fmt"
"io"
"math"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// A Stream is an ordered byte stream.
@@ -254,6 +256,11 @@ func (s *Stream) Read(b []byte) (n int, err error) {
s.conn.handleStreamBytesReadOffLoop(bytesRead) // must be done with ingate unlocked
}()
if s.inresetcode != -1 {
+ if s.inresetcode == streamResetByConnClose {
+ if err := s.conn.finalError(); err != nil {
+ return 0, err
+ }
+ }
return 0, fmt.Errorf("stream reset by peer: %w", StreamErrorCode(s.inresetcode))
}
if s.inclosed.isSet() {
@@ -308,7 +315,7 @@ func (s *Stream) ReadByte() (byte, error) {
var b [1]byte
n, err := s.Read(b[:])
if n > 0 {
- return b[0], err
+ return b[0], nil
}
return 0, err
}
@@ -352,13 +359,9 @@ func (s *Stream) Write(b []byte) (n int, err error) {
// write blocked. (Unlike traditional condition variables, gates do not
// have spurious wakeups.)
}
- if s.outreset.isSet() {
- s.outUnlock()
- return n, errors.New("write to reset stream")
- }
- if s.outclosed.isSet() {
+ if err := s.writeErrorLocked(); err != nil {
s.outUnlock()
- return n, errors.New("write to closed stream")
+ return n, err
}
if len(b) == 0 {
break
@@ -418,7 +421,7 @@ func (s *Stream) Write(b []byte) (n int, err error) {
return n, nil
}
-// WriteBytes writes a single byte to the stream.
+// WriteByte writes a single byte to the stream.
func (s *Stream) WriteByte(c byte) error {
if s.outbufoff < len(s.outbuf) {
s.outbuf[s.outbufoff] = c
@@ -445,10 +448,34 @@ func (s *Stream) flushFastOutputBuffer() {
// Flush flushes data written to the stream.
// It does not wait for the peer to acknowledge receipt of the data.
// Use Close to wait for the peer's acknowledgement.
-func (s *Stream) Flush() {
+func (s *Stream) Flush() error {
+ if s.IsReadOnly() {
+ return errors.New("flush of read-only stream")
+ }
s.outgate.lock()
defer s.outUnlock()
+ if err := s.writeErrorLocked(); err != nil {
+ return err
+ }
s.flushLocked()
+ return nil
+}
+
+// writeErrorLocked returns the error (if any) which should be returned by write operations
+// due to the stream being reset or closed.
+func (s *Stream) writeErrorLocked() error {
+ if s.outreset.isSet() {
+ if s.outresetcode == streamResetByConnClose {
+ if err := s.conn.finalError(); err != nil {
+ return err
+ }
+ }
+ return errors.New("write to reset stream")
+ }
+ if s.outclosed.isSet() {
+ return errors.New("write to closed stream")
+ }
+ return nil
}
func (s *Stream) flushLocked() {
@@ -560,8 +587,8 @@ func (s *Stream) resetInternal(code uint64, userClosed bool) {
if s.outreset.isSet() {
return
}
- if code > maxVarint {
- code = maxVarint
+ if code > quicwire.MaxVarint {
+ code = quicwire.MaxVarint
}
// We could check here to see if the stream is closed and the
// peer has acked all the data and the FIN, but sending an
@@ -595,8 +622,11 @@ func (s *Stream) connHasClosed() {
s.outgate.lock()
if localClose {
s.outclosed.set()
+ s.outreset.set()
+ } else {
+ s.outresetcode = streamResetByConnClose
+ s.outreset.setReceived()
}
- s.outreset.set()
s.outUnlock()
}
diff --git a/quic/stream_test.go b/quic/stream_test.go
index 9f857f29d4..2643ae3dba 100644
--- a/quic/stream_test.go
+++ b/quic/stream_test.go
@@ -15,6 +15,8 @@ import (
"io"
"strings"
"testing"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
func TestStreamWriteBlockedByOutputBuffer(t *testing.T) {
@@ -566,6 +568,25 @@ func TestStreamReceiveEmptyEOF(t *testing.T) {
})
}
+func TestStreamReadByteFromOneByteStream(t *testing.T) {
+ // ReadByte on the only byte of a stream should not return an error.
+ testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ tc, s := newTestConnAndRemoteStream(t, serverSide, styp, permissiveTransportParameters)
+ want := byte(1)
+ tc.writeFrames(packetType1RTT, debugFrameStream{
+ id: s.id,
+ data: []byte{want},
+ fin: true,
+ })
+ if got, err := s.ReadByte(); got != want || err != nil {
+ t.Fatalf("s.ReadByte() = %v, %v; want %v, nil", got, err, want)
+ }
+ if got, err := s.ReadByte(); err != io.EOF {
+ t.Fatalf("s.ReadByte() = %v, %v; want _, EOF", got, err)
+ }
+ })
+}
+
func finalSizeTest(t *testing.T, wantErr transportError, f func(tc *testConn, sid streamID) (finalSize int64), opts ...any) {
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
for _, test := range []struct {
@@ -1324,6 +1345,61 @@ func TestStreamFlushExplicit(t *testing.T) {
})
}
+func TestStreamFlushClosedStream(t *testing.T) {
+ _, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
+ permissiveTransportParameters)
+ s.Close()
+ if err := s.Flush(); err == nil {
+ t.Errorf("s.Flush of closed stream = nil, want error")
+ }
+}
+
+func TestStreamFlushResetStream(t *testing.T) {
+ _, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
+ permissiveTransportParameters)
+ s.Reset(0)
+ if err := s.Flush(); err == nil {
+ t.Errorf("s.Flush of reset stream = nil, want error")
+ }
+}
+
+func TestStreamFlushStreamAfterPeerStopSending(t *testing.T) {
+ tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
+ permissiveTransportParameters)
+ s.Flush() // create the stream
+ tc.wantFrame("stream created after flush",
+ packetType1RTT, debugFrameStream{
+ id: s.id,
+ data: []byte{},
+ })
+
+ // Peer sends a STOP_SENDING.
+ tc.writeFrames(packetType1RTT, debugFrameStopSending{
+ id: s.id,
+ })
+ if err := s.Flush(); err == nil {
+ t.Errorf("s.Flush of stream reset by peer = nil, want error")
+ }
+}
+
+func TestStreamErrorsAfterConnectionClosed(t *testing.T) {
+ tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
+ permissiveTransportParameters)
+ wantErr := &ApplicationError{Code: 42}
+ tc.writeFrames(packetType1RTT, debugFrameConnectionCloseApplication{
+ code: wantErr.Code,
+ })
+ if _, err := s.Read(make([]byte, 1)); !errors.Is(err, wantErr) {
+ t.Errorf("s.Read on closed connection = %v, want %v", err, wantErr)
+ }
+ if _, err := s.Write(make([]byte, 1)); !errors.Is(err, wantErr) {
+ t.Errorf("s.Write on closed connection = %v, want %v", err, wantErr)
+ }
+ if err := s.Flush(); !errors.Is(err, wantErr) {
+ t.Errorf("s.Flush on closed connection = %v, want %v", err, wantErr)
+ }
+}
+
func TestStreamFlushImplicitExact(t *testing.T) {
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
const writeBufferSize = 4
@@ -1467,10 +1543,10 @@ func newRemoteStream(t *testing.T, tc *testConn, styp streamType) *Stream {
func permissiveTransportParameters(p *transportParameters) {
p.initialMaxStreamsBidi = maxStreamsLimit
p.initialMaxStreamsUni = maxStreamsLimit
- p.initialMaxData = maxVarint
- p.initialMaxStreamDataBidiRemote = maxVarint
- p.initialMaxStreamDataBidiLocal = maxVarint
- p.initialMaxStreamDataUni = maxVarint
+ p.initialMaxData = quicwire.MaxVarint
+ p.initialMaxStreamDataBidiRemote = quicwire.MaxVarint
+ p.initialMaxStreamDataBidiLocal = quicwire.MaxVarint
+ p.initialMaxStreamDataUni = quicwire.MaxVarint
}
func makeTestData(n int) []byte {
diff --git a/quic/tlsconfig_test.go b/quic/tlsconfig_test.go
index 5ed9818d57..e24cef08ae 100644
--- a/quic/tlsconfig_test.go
+++ b/quic/tlsconfig_test.go
@@ -8,7 +8,8 @@ package quic
import (
"crypto/tls"
- "strings"
+
+ "golang.org/x/net/internal/testcert"
)
func newTestTLSConfig(side connSide) *tls.Config {
@@ -47,35 +48,9 @@ func newTestTLSConfigWithMoreDefaults(side connSide) *tls.Config {
}
var testCert = func() tls.Certificate {
- cert, err := tls.X509KeyPair(localhostCert, localhostKey)
+ cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
if err != nil {
panic(err)
}
return cert
}()
-
-// localhostCert is a PEM-encoded TLS cert with SAN IPs
-// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
-// generated from src/crypto/tls:
-// go run generate_cert.go --ecdsa-curve P256 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
-var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
-MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO
-BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa
-MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh
-WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms
-PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK
-BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC
-Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
-AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40
-HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE
------END CERTIFICATE-----`)
-
-// localhostKey is the private key for localhostCert.
-var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs
-rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd
-hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9
------END TESTING KEY-----`))
-
-// testingKey helps keep security scanners from getting excited about a private key in this file.
-func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
diff --git a/quic/transport_params.go b/quic/transport_params.go
index 3cc56f4e44..13d1c7c7d5 100644
--- a/quic/transport_params.go
+++ b/quic/transport_params.go
@@ -10,6 +10,8 @@ import (
"encoding/binary"
"net/netip"
"time"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
// transportParameters transferred in the quic_transport_parameters TLS extension.
@@ -77,89 +79,89 @@ const (
func marshalTransportParameters(p transportParameters) []byte {
var b []byte
if v := p.originalDstConnID; v != nil {
- b = appendVarint(b, paramOriginalDestinationConnectionID)
- b = appendVarintBytes(b, v)
+ b = quicwire.AppendVarint(b, paramOriginalDestinationConnectionID)
+ b = quicwire.AppendVarintBytes(b, v)
}
if v := uint64(p.maxIdleTimeout / time.Millisecond); v != 0 {
- b = appendVarint(b, paramMaxIdleTimeout)
- b = appendVarint(b, uint64(sizeVarint(v)))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramMaxIdleTimeout)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(v)))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.statelessResetToken; v != nil {
- b = appendVarint(b, paramStatelessResetToken)
- b = appendVarintBytes(b, v)
+ b = quicwire.AppendVarint(b, paramStatelessResetToken)
+ b = quicwire.AppendVarintBytes(b, v)
}
if v := p.maxUDPPayloadSize; v != defaultParamMaxUDPPayloadSize {
- b = appendVarint(b, paramMaxUDPPayloadSize)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramMaxUDPPayloadSize)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxData; v != 0 {
- b = appendVarint(b, paramInitialMaxData)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxData)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataBidiLocal; v != 0 {
- b = appendVarint(b, paramInitialMaxStreamDataBidiLocal)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxStreamDataBidiLocal)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataBidiRemote; v != 0 {
- b = appendVarint(b, paramInitialMaxStreamDataBidiRemote)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxStreamDataBidiRemote)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataUni; v != 0 {
- b = appendVarint(b, paramInitialMaxStreamDataUni)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxStreamDataUni)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxStreamsBidi; v != 0 {
- b = appendVarint(b, paramInitialMaxStreamsBidi)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxStreamsBidi)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialMaxStreamsUni; v != 0 {
- b = appendVarint(b, paramInitialMaxStreamsUni)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramInitialMaxStreamsUni)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.ackDelayExponent; v != defaultParamAckDelayExponent {
- b = appendVarint(b, paramAckDelayExponent)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramAckDelayExponent)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := uint64(p.maxAckDelay / time.Millisecond); v != defaultParamMaxAckDelayMilliseconds {
- b = appendVarint(b, paramMaxAckDelay)
- b = appendVarint(b, uint64(sizeVarint(v)))
- b = appendVarint(b, v)
+ b = quicwire.AppendVarint(b, paramMaxAckDelay)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(v)))
+ b = quicwire.AppendVarint(b, v)
}
if p.disableActiveMigration {
- b = appendVarint(b, paramDisableActiveMigration)
+ b = quicwire.AppendVarint(b, paramDisableActiveMigration)
b = append(b, 0) // 0-length value
}
if p.preferredAddrConnID != nil {
b = append(b, paramPreferredAddress)
- b = appendVarint(b, uint64(4+2+16+2+1+len(p.preferredAddrConnID)+16))
+ b = quicwire.AppendVarint(b, uint64(4+2+16+2+1+len(p.preferredAddrConnID)+16))
b = append(b, p.preferredAddrV4.Addr().AsSlice()...) // 4 bytes
b = binary.BigEndian.AppendUint16(b, p.preferredAddrV4.Port()) // 2 bytes
b = append(b, p.preferredAddrV6.Addr().AsSlice()...) // 16 bytes
b = binary.BigEndian.AppendUint16(b, p.preferredAddrV6.Port()) // 2 bytes
- b = appendUint8Bytes(b, p.preferredAddrConnID) // 1 byte + len(conn_id)
+ b = quicwire.AppendUint8Bytes(b, p.preferredAddrConnID) // 1 byte + len(conn_id)
b = append(b, p.preferredAddrResetToken...) // 16 bytes
}
if v := p.activeConnIDLimit; v != defaultParamActiveConnIDLimit {
- b = appendVarint(b, paramActiveConnectionIDLimit)
- b = appendVarint(b, uint64(sizeVarint(uint64(v))))
- b = appendVarint(b, uint64(v))
+ b = quicwire.AppendVarint(b, paramActiveConnectionIDLimit)
+ b = quicwire.AppendVarint(b, uint64(quicwire.SizeVarint(uint64(v))))
+ b = quicwire.AppendVarint(b, uint64(v))
}
if v := p.initialSrcConnID; v != nil {
- b = appendVarint(b, paramInitialSourceConnectionID)
- b = appendVarintBytes(b, v)
+ b = quicwire.AppendVarint(b, paramInitialSourceConnectionID)
+ b = quicwire.AppendVarintBytes(b, v)
}
if v := p.retrySrcConnID; v != nil {
- b = appendVarint(b, paramRetrySourceConnectionID)
- b = appendVarintBytes(b, v)
+ b = quicwire.AppendVarint(b, paramRetrySourceConnectionID)
+ b = quicwire.AppendVarintBytes(b, v)
}
return b
}
@@ -167,12 +169,12 @@ func marshalTransportParameters(p transportParameters) []byte {
func unmarshalTransportParams(params []byte) (transportParameters, error) {
p := defaultTransportParameters()
for len(params) > 0 {
- id, n := consumeVarint(params)
+ id, n := quicwire.ConsumeVarint(params)
if n < 0 {
return p, localTransportError{code: errTransportParameter}
}
params = params[n:]
- val, n := consumeVarintBytes(params)
+ val, n := quicwire.ConsumeVarintBytes(params)
if n < 0 {
return p, localTransportError{code: errTransportParameter}
}
@@ -184,7 +186,7 @@ func unmarshalTransportParams(params []byte) (transportParameters, error) {
n = len(val)
case paramMaxIdleTimeout:
var v uint64
- v, n = consumeVarint(val)
+ v, n = quicwire.ConsumeVarint(val)
// If this is unreasonably large, consider it as no timeout to avoid
// time.Duration overflows.
if v > 1<<32 {
@@ -198,38 +200,38 @@ func unmarshalTransportParams(params []byte) (transportParameters, error) {
p.statelessResetToken = val
n = 16
case paramMaxUDPPayloadSize:
- p.maxUDPPayloadSize, n = consumeVarintInt64(val)
+ p.maxUDPPayloadSize, n = quicwire.ConsumeVarintInt64(val)
if p.maxUDPPayloadSize < 1200 {
return p, localTransportError{code: errTransportParameter}
}
case paramInitialMaxData:
- p.initialMaxData, n = consumeVarintInt64(val)
+ p.initialMaxData, n = quicwire.ConsumeVarintInt64(val)
case paramInitialMaxStreamDataBidiLocal:
- p.initialMaxStreamDataBidiLocal, n = consumeVarintInt64(val)
+ p.initialMaxStreamDataBidiLocal, n = quicwire.ConsumeVarintInt64(val)
case paramInitialMaxStreamDataBidiRemote:
- p.initialMaxStreamDataBidiRemote, n = consumeVarintInt64(val)
+ p.initialMaxStreamDataBidiRemote, n = quicwire.ConsumeVarintInt64(val)
case paramInitialMaxStreamDataUni:
- p.initialMaxStreamDataUni, n = consumeVarintInt64(val)
+ p.initialMaxStreamDataUni, n = quicwire.ConsumeVarintInt64(val)
case paramInitialMaxStreamsBidi:
- p.initialMaxStreamsBidi, n = consumeVarintInt64(val)
+ p.initialMaxStreamsBidi, n = quicwire.ConsumeVarintInt64(val)
if p.initialMaxStreamsBidi > maxStreamsLimit {
return p, localTransportError{code: errTransportParameter}
}
case paramInitialMaxStreamsUni:
- p.initialMaxStreamsUni, n = consumeVarintInt64(val)
+ p.initialMaxStreamsUni, n = quicwire.ConsumeVarintInt64(val)
if p.initialMaxStreamsUni > maxStreamsLimit {
return p, localTransportError{code: errTransportParameter}
}
case paramAckDelayExponent:
var v uint64
- v, n = consumeVarint(val)
+ v, n = quicwire.ConsumeVarint(val)
if v > 20 {
return p, localTransportError{code: errTransportParameter}
}
p.ackDelayExponent = int8(v)
case paramMaxAckDelay:
var v uint64
- v, n = consumeVarint(val)
+ v, n = quicwire.ConsumeVarint(val)
if v >= 1<<14 {
return p, localTransportError{code: errTransportParameter}
}
@@ -251,7 +253,7 @@ func unmarshalTransportParams(params []byte) (transportParameters, error) {
)
val = val[16+2:]
var nn int
- p.preferredAddrConnID, nn = consumeUint8Bytes(val)
+ p.preferredAddrConnID, nn = quicwire.ConsumeUint8Bytes(val)
if nn < 0 {
return p, localTransportError{code: errTransportParameter}
}
@@ -262,7 +264,7 @@ func unmarshalTransportParams(params []byte) (transportParameters, error) {
p.preferredAddrResetToken = val
val = nil
case paramActiveConnectionIDLimit:
- p.activeConnIDLimit, n = consumeVarintInt64(val)
+ p.activeConnIDLimit, n = quicwire.ConsumeVarintInt64(val)
if p.activeConnIDLimit < 2 {
return p, localTransportError{code: errTransportParameter}
}
diff --git a/quic/transport_params_test.go b/quic/transport_params_test.go
index cc88e83fd6..f1961178e8 100644
--- a/quic/transport_params_test.go
+++ b/quic/transport_params_test.go
@@ -13,6 +13,8 @@ import (
"reflect"
"testing"
"time"
+
+ "golang.org/x/net/internal/quic/quicwire"
)
func TestTransportParametersMarshalUnmarshal(t *testing.T) {
@@ -334,9 +336,9 @@ func TestTransportParameterMaxIdleTimeoutOverflowsDuration(t *testing.T) {
tooManyMS := 1 + (math.MaxInt64 / uint64(time.Millisecond))
var enc []byte
- enc = appendVarint(enc, paramMaxIdleTimeout)
- enc = appendVarint(enc, uint64(sizeVarint(tooManyMS)))
- enc = appendVarint(enc, uint64(tooManyMS))
+ enc = quicwire.AppendVarint(enc, paramMaxIdleTimeout)
+ enc = quicwire.AppendVarint(enc, uint64(quicwire.SizeVarint(tooManyMS)))
+ enc = quicwire.AppendVarint(enc, uint64(tooManyMS))
dec, err := unmarshalTransportParams(enc)
if err != nil {
diff --git a/quic/udp_packetconn.go b/quic/udp_packetconn.go
new file mode 100644
index 0000000000..85ce349ff1
--- /dev/null
+++ b/quic/udp_packetconn.go
@@ -0,0 +1,69 @@
+// 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.
+
+//go:build go1.21
+
+package quic
+
+import (
+ "net"
+ "net/netip"
+)
+
+// netPacketConn is a packetConn implementation wrapping a net.PacketConn.
+//
+// This is mostly useful for tests, since PacketConn doesn't provide access to
+// important features such as identifying the local address packets were received on.
+type netPacketConn struct {
+ c net.PacketConn
+ localAddr netip.AddrPort
+}
+
+func newNetPacketConn(pc net.PacketConn) (*netPacketConn, error) {
+ addr, err := addrPortFromAddr(pc.LocalAddr())
+ if err != nil {
+ return nil, err
+ }
+ return &netPacketConn{
+ c: pc,
+ localAddr: addr,
+ }, nil
+}
+
+func (c *netPacketConn) Close() error {
+ return c.c.Close()
+}
+
+func (c *netPacketConn) LocalAddr() netip.AddrPort {
+ return c.localAddr
+}
+
+func (c *netPacketConn) Read(f func(*datagram)) {
+ for {
+ dgram := newDatagram()
+ n, peerAddr, err := c.c.ReadFrom(dgram.b)
+ if err != nil {
+ return
+ }
+ dgram.peerAddr, err = addrPortFromAddr(peerAddr)
+ if err != nil {
+ continue
+ }
+ dgram.b = dgram.b[:n]
+ f(dgram)
+ }
+}
+
+func (c *netPacketConn) Write(dgram datagram) error {
+ _, err := c.c.WriteTo(dgram.b, net.UDPAddrFromAddrPort(dgram.peerAddr))
+ return err
+}
+
+func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
+ switch a := addr.(type) {
+ case *net.UDPAddr:
+ return a.AddrPort(), nil
+ }
+ return netip.ParseAddrPort(addr.String())
+}
diff --git a/route/address.go b/route/address.go
index b649f43141..492838a7fe 100644
--- a/route/address.go
+++ b/route/address.go
@@ -171,45 +171,55 @@ func (a *Inet6Addr) marshal(b []byte) (int, error) {
// parseInetAddr parses b as an internet address for IPv4 or IPv6.
func parseInetAddr(af int, b []byte) (Addr, error) {
const (
- off4 = 4 // offset of in_addr
- off6 = 8 // offset of in6_addr
+ off4 = 4 // offset of in_addr
+ off6 = 8 // offset of in6_addr
+ ipv4Len = 4 // length of IPv4 address in bytes
+ ipv6Len = 16 // length of IPv6 address in bytes
)
switch af {
case syscall.AF_INET:
- if len(b) < (off4+1) || len(b) < int(b[0]) || b[0] == 0 {
+ if len(b) < int(b[0]) {
return nil, errInvalidAddr
}
sockAddrLen := int(b[0])
a := &Inet4Addr{}
- n := off4 + 4
- if sockAddrLen < n {
- n = sockAddrLen
+ // sockAddrLen of 0 is valid and represents 0.0.0.0
+ if sockAddrLen > off4 {
+ // Calculate how many bytes of the address to copy:
+ // either full IPv4 length or the available length.
+ n := off4 + ipv4Len
+ if sockAddrLen < n {
+ n = sockAddrLen
+ }
+ copy(a.IP[:], b[off4:n])
}
- copy(a.IP[:], b[off4:n])
return a, nil
case syscall.AF_INET6:
- if len(b) < (off6+1) || len(b) < int(b[0]) || b[0] == 0 {
+ if len(b) < int(b[0]) {
return nil, errInvalidAddr
}
sockAddrLen := int(b[0])
- n := off6 + 16
- if sockAddrLen < n {
- n = sockAddrLen
- }
a := &Inet6Addr{}
- if sockAddrLen == sizeofSockaddrInet6 {
- a.ZoneID = int(nativeEndian.Uint32(b[24:28]))
- }
- copy(a.IP[:], b[off6:n])
- if a.IP[0] == 0xfe && a.IP[1]&0xc0 == 0x80 || a.IP[0] == 0xff && (a.IP[1]&0x0f == 0x01 || a.IP[1]&0x0f == 0x02) {
- // KAME based IPv6 protocol stack usually
- // embeds the interface index in the
- // interface-local or link-local address as
- // the kernel-internal form.
- id := int(bigEndian.Uint16(a.IP[2:4]))
- if id != 0 {
- a.ZoneID = id
- a.IP[2], a.IP[3] = 0, 0
+ // sockAddrLen of 0 is valid and represents ::
+ if sockAddrLen > off6 {
+ n := off6 + ipv6Len
+ if sockAddrLen < n {
+ n = sockAddrLen
+ }
+ if sockAddrLen == sizeofSockaddrInet6 {
+ a.ZoneID = int(nativeEndian.Uint32(b[24:28]))
+ }
+ copy(a.IP[:], b[off6:n])
+ if a.IP[0] == 0xfe && a.IP[1]&0xc0 == 0x80 || a.IP[0] == 0xff && (a.IP[1]&0x0f == 0x01 || a.IP[1]&0x0f == 0x02) {
+ // KAME based IPv6 protocol stack usually
+ // embeds the interface index in the
+ // interface-local or link-local address as
+ // the kernel-internal form.
+ id := int(bigEndian.Uint16(a.IP[2:4]))
+ if id != 0 {
+ a.ZoneID = id
+ a.IP[2], a.IP[3] = 0, 0
+ }
}
}
return a, nil
@@ -386,13 +396,19 @@ func marshalAddrs(b []byte, as []Addr) (uint, error) {
func parseAddrs(attrs uint, fn func(int, []byte) (int, Addr, error), b []byte) ([]Addr, error) {
var as [syscall.RTAX_MAX]Addr
af := int(syscall.AF_UNSPEC)
+ isInet := func(fam int) bool {
+ return fam == syscall.AF_INET || fam == syscall.AF_INET6
+ }
+ isMask := func(addrType uint) bool {
+ return addrType == syscall.RTAX_NETMASK || addrType == syscall.RTAX_GENMASK
+ }
for i := uint(0); i < syscall.RTAX_MAX && len(b) >= roundup(0); i++ {
if attrs&(1< 0 {
+ case isInet(int(b[1])) || (isMask(i) && isInet(af)):
+ if isInet(int(b[1])) {
af = int(b[1])
- a, err := parseInetAddr(af, b)
- if err != nil {
- return nil, err
- }
- as[i] = a
}
+ a, err := parseInetAddr(af, b)
+ if err != nil {
+ return nil, err
+ }
+ as[i] = a
l := roundup(int(b[0]))
if len(b) < l {
return nil, errMessageTooShort
diff --git a/route/address_darwin_test.go b/route/address_darwin_test.go
index add72e37ec..e7e666ab30 100644
--- a/route/address_darwin_test.go
+++ b/route/address_darwin_test.go
@@ -29,12 +29,12 @@ var parseAddrsOnDarwinLittleEndianTests = []parseAddrsOnDarwinTest{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0,
- 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0,
},
[]Addr{
&Inet4Addr{IP: [4]byte{192, 168, 86, 0}},
&LinkAddr{Index: 4},
- &Inet4Addr{IP: [4]byte{255, 255, 255, 255}},
+ &Inet4Addr{IP: [4]byte{255, 255, 255, 0}},
nil,
nil,
nil,
@@ -96,6 +96,38 @@ var parseAddrsOnDarwinLittleEndianTests = []parseAddrsOnDarwinTest{
nil,
},
},
+ // sudo route -n add -inet6 -ifscope en11 -net :: -netmask :: fe80::2d0:4cff:fe10:15d2
+ // RTM_ADD: Add Route: len 152, pid: 81198, seq 1, errno 0, ifscope 13, flags:
+ // locks: inits:
+ // sockaddrs:
+ // :: fe80::2d0:4cff:fe10:15d2 ::
+ {
+ syscall.RTA_DST | syscall.RTA_GATEWAY | syscall.RTA_NETMASK,
+ parseKernelInetAddr,
+ []byte{
+ 0x1c, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x1c, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0xd0, 0x4c, 0xff, 0xfe, 0x10, 0x15, 0xd2,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x02, 0x1e, 0x00, 0x00,
+ },
+ []Addr{
+ &Inet6Addr{},
+ &Inet6Addr{IP: [16]byte{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x4c, 0xff, 0xfe, 0x10, 0x15, 0xd2}},
+ &Inet6Addr{},
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ },
// golang/go#70528, the kernel can produce addresses of length 0
{
syscall.RTA_DST | syscall.RTA_GATEWAY | syscall.RTA_NETMASK,
@@ -112,7 +144,7 @@ var parseAddrsOnDarwinLittleEndianTests = []parseAddrsOnDarwinTest{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
},
[]Addr{
- nil,
+ &Inet6Addr{IP: [16]byte{}},
&Inet6Addr{IP: [16]byte{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x2f, 0x4b, 0xff, 0xfe, 0x09, 0x3b, 0xff}, ZoneID: 33},
&Inet6Addr{IP: [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
nil,
diff --git a/route/example_darwin_test.go b/route/example_darwin_test.go
new file mode 100644
index 0000000000..e442c3ecf7
--- /dev/null
+++ b/route/example_darwin_test.go
@@ -0,0 +1,70 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package route_test
+
+import (
+ "fmt"
+ "net/netip"
+ "os"
+ "syscall"
+
+ "golang.org/x/net/route"
+ "golang.org/x/sys/unix"
+)
+
+// This example demonstrates how to parse a response to RTM_GET request.
+func ExampleParseRIB() {
+ fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
+ if err != nil {
+ return
+ }
+ defer unix.Close(fd)
+
+ // Create a RouteMessage with RTM_GET type
+ rtm := &route.RouteMessage{
+ Version: syscall.RTM_VERSION,
+ Type: unix.RTM_GET,
+ ID: uintptr(os.Getpid()),
+ Seq: 0,
+ Addrs: []route.Addr{
+ &route.Inet4Addr{IP: [4]byte{127, 0, 0, 0}},
+ },
+ }
+
+ // Marshal the message into bytes
+ msgBytes, err := rtm.Marshal()
+ if err != nil {
+ return
+ }
+
+ // Send the message over the routing socket
+ _, err = unix.Write(fd, msgBytes)
+ if err != nil {
+ return
+ }
+
+ // Read the response from the routing socket
+ var buf [2 << 10]byte
+ n, err := unix.Read(fd, buf[:])
+ if err != nil {
+ return
+ }
+
+ // Parse the response messages
+ msgs, err := route.ParseRIB(route.RIBTypeRoute, buf[:n])
+ if err != nil {
+ return
+ }
+ routeMsg, ok := msgs[0].(*route.RouteMessage)
+ if !ok {
+ return
+ }
+ netmask, ok := routeMsg.Addrs[2].(*route.Inet4Addr)
+ if !ok {
+ return
+ }
+ fmt.Println(netip.AddrFrom4(netmask.IP))
+ // Output: 255.0.0.0
+}
diff --git a/route/sys_netbsd.go b/route/sys_netbsd.go
index be4460e13f..c6bb6bc8a2 100644
--- a/route/sys_netbsd.go
+++ b/route/sys_netbsd.go
@@ -25,7 +25,7 @@ func (m *RouteMessage) Sys() []Sys {
}
}
-// RouteMetrics represents route metrics.
+// InterfaceMetrics represents route metrics.
type InterfaceMetrics struct {
Type int // interface type
MTU int // maximum transmission unit
diff --git a/webdav/file_test.go b/webdav/file_test.go
index 3af53fde31..c9313dc5bb 100644
--- a/webdav/file_test.go
+++ b/webdav/file_test.go
@@ -517,12 +517,7 @@ func TestDir(t *testing.T) {
t.Skip("see golang.org/issue/11453")
}
- td, err := os.MkdirTemp("", "webdav-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(td)
- testFS(t, Dir(td))
+ testFS(t, Dir(t.TempDir()))
}
func TestMemFS(t *testing.T) {
diff --git a/websocket/websocket.go b/websocket/websocket.go
index ac76165ceb..3448d20395 100644
--- a/websocket/websocket.go
+++ b/websocket/websocket.go
@@ -6,9 +6,10 @@
// as specified in RFC 6455.
//
// This package currently lacks some features found in an alternative
-// and more actively maintained WebSocket package:
+// and more actively maintained WebSocket packages:
//
-// https://pkg.go.dev/github.com/coder/websocket
+// - [github.com/gorilla/websocket]
+// - [github.com/coder/websocket]
package websocket // import "golang.org/x/net/websocket"
import (