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 (