diff --git a/go.mod b/go.mod
index a92c15011a..4d8f58967e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module golang.org/x/net
go 1.17
require (
- golang.org/x/sys v0.1.0
- golang.org/x/term v0.1.0
- golang.org/x/text v0.4.0
+ golang.org/x/sys v0.5.0
+ golang.org/x/term v0.5.0
+ golang.org/x/text v0.7.0
)
diff --git a/go.sum b/go.sum
index dd80c1a10b..bcd80060dd 100644
--- a/go.sum
+++ b/go.sum
@@ -12,17 +12,17 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
-golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/html/comment_test.go b/html/comment_test.go
new file mode 100644
index 0000000000..2c80bc748c
--- /dev/null
+++ b/html/comment_test.go
@@ -0,0 +1,270 @@
+// Copyright 2023 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 html
+
+import (
+ "bytes"
+ "testing"
+)
+
+// TestComments exhaustively tests every 'interesting' N-byte string is
+// correctly parsed as a comment. N ranges from 4+1 to 4+suffixLen inclusive,
+// where 4 is the length of the "")
return
}
@@ -628,17 +632,50 @@ func (z *Tokenizer) readComment() {
if dashCount >= 2 {
c = z.readByte()
if z.err != nil {
- z.data.end = z.raw.end
+ z.data.end = z.calculateAbruptCommentDataEnd()
return
- }
- if c == '>' {
+ } else if c == '>' {
z.data.end = z.raw.end - len("--!>")
return
+ } else if c == '-' {
+ dashCount = 1
+ beginning = false
+ continue
}
}
}
dashCount = 0
+ beginning = false
+ }
+}
+
+func (z *Tokenizer) calculateAbruptCommentDataEnd() int {
+ raw := z.Raw()
+ const prefixLen = len("",
},
- // Comments.
+ // Comments. See also func TestComments.
{
"comment0",
"abcdef",
@@ -366,6 +366,51 @@ var tokenTests = []tokenTest{
"az",
"a$$z",
},
+ {
+ "comment14",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment15",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment16",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment17",
+ "a",
+ },
+ {
+ "comment18",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment19",
+ "a",
+ },
+ {
+ "comment20",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment21",
+ "az",
+ "a$$z",
+ },
+ {
+ "comment22",
+ "az",
+ "a$$z",
+ },
// An attribute with a backslash.
{
"backslash",
@@ -456,26 +501,27 @@ var tokenTests = []tokenTest{
}
func TestTokenizer(t *testing.T) {
-loop:
for _, tt := range tokenTests {
- z := NewTokenizer(strings.NewReader(tt.html))
- if tt.golden != "" {
- for i, s := range strings.Split(tt.golden, "$") {
- if z.Next() == ErrorToken {
- t.Errorf("%s token %d: want %q got error %v", tt.desc, i, s, z.Err())
- continue loop
- }
- actual := z.Token().String()
- if s != actual {
- t.Errorf("%s token %d: want %q got %q", tt.desc, i, s, actual)
- continue loop
+ t.Run(tt.desc, func(t *testing.T) {
+ z := NewTokenizer(strings.NewReader(tt.html))
+ if tt.golden != "" {
+ for i, s := range strings.Split(tt.golden, "$") {
+ if z.Next() == ErrorToken {
+ t.Errorf("%s token %d: want %q got error %v", tt.desc, i, s, z.Err())
+ return
+ }
+ actual := z.Token().String()
+ if s != actual {
+ t.Errorf("%s token %d: want %q got %q", tt.desc, i, s, actual)
+ return
+ }
}
}
- }
- z.Next()
- if z.Err() != io.EOF {
- t.Errorf("%s: want EOF got %q", tt.desc, z.Err())
- }
+ z.Next()
+ if z.Err() != io.EOF {
+ t.Errorf("%s: want EOF got %q", tt.desc, z.Err())
+ }
+ })
}
}
diff --git a/http2/flow.go b/http2/flow.go
index b51f0e0cf1..b7dbd18695 100644
--- a/http2/flow.go
+++ b/http2/flow.go
@@ -6,23 +6,91 @@
package http2
-// flow is the flow control window's size.
-type flow struct {
+// inflowMinRefresh is the minimum number of bytes we'll send for a
+// flow control window update.
+const inflowMinRefresh = 4 << 10
+
+// inflow accounts for an inbound flow control window.
+// It tracks both the latest window sent to the peer (used for enforcement)
+// and the accumulated unsent window.
+type inflow struct {
+ avail int32
+ unsent int32
+}
+
+// init sets the initial window.
+func (f *inflow) init(n int32) {
+ f.avail = n
+}
+
+// add adds n bytes to the window, with a maximum window size of max,
+// indicating that the peer can now send us more data.
+// For example, the user read from a {Request,Response} body and consumed
+// some of the buffered data, so the peer can now send more.
+// It returns the number of bytes to send in a WINDOW_UPDATE frame to the peer.
+// Window updates are accumulated and sent when the unsent capacity
+// is at least inflowMinRefresh or will at least double the peer's available window.
+func (f *inflow) add(n int) (connAdd int32) {
+ if n < 0 {
+ panic("negative update")
+ }
+ unsent := int64(f.unsent) + int64(n)
+ // "A sender MUST NOT allow a flow-control window to exceed 2^31-1 octets."
+ // RFC 7540 Section 6.9.1.
+ const maxWindow = 1<<31 - 1
+ if unsent+int64(f.avail) > maxWindow {
+ panic("flow control update exceeds maximum window size")
+ }
+ f.unsent = int32(unsent)
+ if f.unsent < inflowMinRefresh && f.unsent < f.avail {
+ // If there aren't at least inflowMinRefresh bytes of window to send,
+ // and this update won't at least double the window, buffer the update for later.
+ return 0
+ }
+ f.avail += f.unsent
+ f.unsent = 0
+ return int32(unsent)
+}
+
+// take attempts to take n bytes from the peer's flow control window.
+// It reports whether the window has available capacity.
+func (f *inflow) take(n uint32) bool {
+ if n > uint32(f.avail) {
+ return false
+ }
+ f.avail -= int32(n)
+ return true
+}
+
+// takeInflows attempts to take n bytes from two inflows,
+// typically connection-level and stream-level flows.
+// It reports whether both windows have available capacity.
+func takeInflows(f1, f2 *inflow, n uint32) bool {
+ if n > uint32(f1.avail) || n > uint32(f2.avail) {
+ return false
+ }
+ f1.avail -= int32(n)
+ f2.avail -= int32(n)
+ return true
+}
+
+// outflow is the outbound flow control window's size.
+type outflow struct {
_ incomparable
// n is the number of DATA bytes we're allowed to send.
- // A flow is kept both on a conn and a per-stream.
+ // An outflow is kept both on a conn and a per-stream.
n int32
- // conn points to the shared connection-level flow that is
- // shared by all streams on that conn. It is nil for the flow
+ // conn points to the shared connection-level outflow that is
+ // shared by all streams on that conn. It is nil for the outflow
// that's on the conn directly.
- conn *flow
+ conn *outflow
}
-func (f *flow) setConnFlow(cf *flow) { f.conn = cf }
+func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf }
-func (f *flow) available() int32 {
+func (f *outflow) available() int32 {
n := f.n
if f.conn != nil && f.conn.n < n {
n = f.conn.n
@@ -30,7 +98,7 @@ func (f *flow) available() int32 {
return n
}
-func (f *flow) take(n int32) {
+func (f *outflow) take(n int32) {
if n > f.available() {
panic("internal error: took too much")
}
@@ -42,7 +110,7 @@ func (f *flow) take(n int32) {
// add adds n bytes (positive or negative) to the flow control window.
// It returns false if the sum would exceed 2^31-1.
-func (f *flow) add(n int32) bool {
+func (f *outflow) add(n int32) bool {
sum := f.n + n
if (sum > n) == (f.n > 0) {
f.n = sum
diff --git a/http2/flow_test.go b/http2/flow_test.go
index 7ae82c7817..cae4f38c0c 100644
--- a/http2/flow_test.go
+++ b/http2/flow_test.go
@@ -6,9 +6,61 @@ package http2
import "testing"
-func TestFlow(t *testing.T) {
- var st flow
- var conn flow
+func TestInFlowTake(t *testing.T) {
+ var f inflow
+ f.init(100)
+ if !f.take(40) {
+ t.Fatalf("f.take(40) from 100: got false, want true")
+ }
+ if !f.take(40) {
+ t.Fatalf("f.take(40) from 60: got false, want true")
+ }
+ if f.take(40) {
+ t.Fatalf("f.take(40) from 20: got true, want false")
+ }
+ if !f.take(20) {
+ t.Fatalf("f.take(20) from 20: got false, want true")
+ }
+}
+
+func TestInflowAddSmall(t *testing.T) {
+ var f inflow
+ f.init(0)
+ // Adding even a small amount when there is no flow causes an immediate send.
+ if got, want := f.add(1), int32(1); got != want {
+ t.Fatalf("f.add(1) to 1 = %v, want %v", got, want)
+ }
+}
+
+func TestInflowAdd(t *testing.T) {
+ var f inflow
+ f.init(10 * inflowMinRefresh)
+ if got, want := f.add(inflowMinRefresh-1), int32(0); got != want {
+ t.Fatalf("f.add(minRefresh - 1) = %v, want %v", got, want)
+ }
+ if got, want := f.add(1), int32(inflowMinRefresh); got != want {
+ t.Fatalf("f.add(minRefresh) = %v, want %v", got, want)
+ }
+}
+
+func TestTakeInflows(t *testing.T) {
+ var a, b inflow
+ a.init(10)
+ b.init(20)
+ if !takeInflows(&a, &b, 5) {
+ t.Fatalf("takeInflows(a, b, 5) from 10, 20: got false, want true")
+ }
+ if takeInflows(&a, &b, 6) {
+ t.Fatalf("takeInflows(a, b, 6) from 5, 15: got true, want false")
+ }
+ if !takeInflows(&a, &b, 5) {
+ t.Fatalf("takeInflows(a, b, 5) from 5, 15: got false, want true")
+ }
+}
+
+func TestOutFlow(t *testing.T) {
+ var st outflow
+ var conn outflow
st.add(3)
conn.add(2)
@@ -29,8 +81,8 @@ func TestFlow(t *testing.T) {
}
}
-func TestFlowAdd(t *testing.T) {
- var f flow
+func TestOutFlowAdd(t *testing.T) {
+ var f outflow
if !f.add(1) {
t.Fatal("failed to add 1")
}
@@ -51,8 +103,8 @@ func TestFlowAdd(t *testing.T) {
}
}
-func TestFlowAddOverflow(t *testing.T) {
- var f flow
+func TestOutFlowAddOverflow(t *testing.T) {
+ var f outflow
if !f.add(0) {
t.Fatal("failed to add 0")
}
diff --git a/http2/frame.go b/http2/frame.go
index 184ac45feb..c1f6b90dc3 100644
--- a/http2/frame.go
+++ b/http2/frame.go
@@ -662,6 +662,15 @@ func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error {
// It is the caller's responsibility not to violate the maximum frame size
// and to not call other Write methods concurrently.
func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
+ if err := f.startWriteDataPadded(streamID, endStream, data, pad); err != nil {
+ return err
+ }
+ return f.endWrite()
+}
+
+// startWriteDataPadded is WriteDataPadded, but only writes the frame to the Framer's internal buffer.
+// The caller should call endWrite to flush the frame to the underlying writer.
+func (f *Framer) startWriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
if !validStreamID(streamID) && !f.AllowIllegalWrites {
return errStreamID
}
@@ -691,7 +700,7 @@ func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []by
}
f.wbuf = append(f.wbuf, data...)
f.wbuf = append(f.wbuf, pad...)
- return f.endWrite()
+ return nil
}
// A SettingsFrame conveys configuration parameters that affect how
diff --git a/http2/h2c/h2c.go b/http2/h2c/h2c.go
index 2b77ffdaff..a72bbed1bc 100644
--- a/http2/h2c/h2c.go
+++ b/http2/h2c/h2c.go
@@ -109,6 +109,7 @@ func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if http2VerboseLogs {
log.Printf("h2c: error h2c upgrade: %v", err)
}
+ w.WriteHeader(http.StatusInternalServerError)
return
}
defer conn.Close()
@@ -167,7 +168,10 @@ func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []
return nil, nil, errors.New("h2c: connection does not support Hijack")
}
- body, _ := io.ReadAll(r.Body)
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ return nil, nil, err
+ }
r.Body = io.NopCloser(bytes.NewBuffer(body))
conn, rw, err := hijacker.Hijack()
diff --git a/http2/h2c/h2c_test.go b/http2/h2c/h2c_test.go
index 558e597c61..038cbc3649 100644
--- a/http2/h2c/h2c_test.go
+++ b/http2/h2c/h2c_test.go
@@ -8,6 +8,7 @@ import (
"context"
"crypto/tls"
"fmt"
+ "io"
"io/ioutil"
"log"
"net"
@@ -134,3 +135,38 @@ func TestPropagation(t *testing.T) {
t.Fatal("expected server err, got nil")
}
}
+
+func TestMaxBytesHandler(t *testing.T) {
+ const bodyLimit = 10
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("got request, expected to be blocked by body limit")
+ })
+
+ h2s := &http2.Server{}
+ h1s := httptest.NewUnstartedServer(http.MaxBytesHandler(NewHandler(handler, h2s), bodyLimit))
+ h1s.Start()
+ defer h1s.Close()
+
+ // Wrap the body in a struct{io.Reader} to prevent it being rewound and resent.
+ body := "0123456789abcdef"
+ req, err := http.NewRequest("POST", h1s.URL, struct{ io.Reader }{strings.NewReader(body)})
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("Http2-Settings", "")
+ req.Header.Set("Upgrade", "h2c")
+ req.Header.Set("Connection", "Upgrade, HTTP2-Settings")
+
+ resp, err := h1s.Client().Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+ _, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := resp.StatusCode, http.StatusInternalServerError; got != want {
+ t.Errorf("resp.StatusCode = %v, want %v", got, want)
+ }
+}
diff --git a/http2/headermap.go b/http2/headermap.go
index 9e12941da4..149b3dd20e 100644
--- a/http2/headermap.go
+++ b/http2/headermap.go
@@ -27,7 +27,14 @@ func buildCommonHeaderMaps() {
"accept-language",
"accept-ranges",
"age",
+ "access-control-allow-credentials",
+ "access-control-allow-headers",
+ "access-control-allow-methods",
"access-control-allow-origin",
+ "access-control-expose-headers",
+ "access-control-max-age",
+ "access-control-request-headers",
+ "access-control-request-method",
"allow",
"authorization",
"cache-control",
@@ -53,6 +60,7 @@ func buildCommonHeaderMaps() {
"link",
"location",
"max-forwards",
+ "origin",
"proxy-authenticate",
"proxy-authorization",
"range",
@@ -68,6 +76,8 @@ func buildCommonHeaderMaps() {
"vary",
"via",
"www-authenticate",
+ "x-forwarded-for",
+ "x-forwarded-proto",
}
commonLowerHeader = make(map[string]string, len(common))
commonCanonHeader = make(map[string]string, len(common))
@@ -85,3 +95,11 @@ func lowerHeader(v string) (lower string, ascii bool) {
}
return asciiToLower(v)
}
+
+func canonicalHeader(v string) string {
+ buildCommonHeaderMapsOnce()
+ if s, ok := commonCanonHeader[v]; ok {
+ return s
+ }
+ return http.CanonicalHeaderKey(v)
+}
diff --git a/http2/hpack/encode.go b/http2/hpack/encode.go
index 6886dc163c..46219da2b0 100644
--- a/http2/hpack/encode.go
+++ b/http2/hpack/encode.go
@@ -116,6 +116,11 @@ func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
e.dynTab.setMaxSize(v)
}
+// MaxDynamicTableSize returns the current dynamic header table size.
+func (e *Encoder) MaxDynamicTableSize() (v uint32) {
+ return e.dynTab.maxSize
+}
+
// SetMaxDynamicTableSizeLimit changes the maximum value that can be
// specified in SetMaxDynamicTableSize to v. By default, it is set to
// 4096, which is the same size of the default dynamic header table
diff --git a/http2/hpack/gen.go b/http2/hpack/gen.go
new file mode 100644
index 0000000000..de14ab0ec0
--- /dev/null
+++ b/http2/hpack/gen.go
@@ -0,0 +1,184 @@
+// Copyright 2022 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 ignore
+// +build ignore
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/format"
+ "io/ioutil"
+ "os"
+ "sort"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
+var staticTableEntries = [...]hpack.HeaderField{
+ {Name: ":authority"},
+ {Name: ":method", Value: "GET"},
+ {Name: ":method", Value: "POST"},
+ {Name: ":path", Value: "/"},
+ {Name: ":path", Value: "/index.html"},
+ {Name: ":scheme", Value: "http"},
+ {Name: ":scheme", Value: "https"},
+ {Name: ":status", Value: "200"},
+ {Name: ":status", Value: "204"},
+ {Name: ":status", Value: "206"},
+ {Name: ":status", Value: "304"},
+ {Name: ":status", Value: "400"},
+ {Name: ":status", Value: "404"},
+ {Name: ":status", Value: "500"},
+ {Name: "accept-charset"},
+ {Name: "accept-encoding", Value: "gzip, deflate"},
+ {Name: "accept-language"},
+ {Name: "accept-ranges"},
+ {Name: "accept"},
+ {Name: "access-control-allow-origin"},
+ {Name: "age"},
+ {Name: "allow"},
+ {Name: "authorization"},
+ {Name: "cache-control"},
+ {Name: "content-disposition"},
+ {Name: "content-encoding"},
+ {Name: "content-language"},
+ {Name: "content-length"},
+ {Name: "content-location"},
+ {Name: "content-range"},
+ {Name: "content-type"},
+ {Name: "cookie"},
+ {Name: "date"},
+ {Name: "etag"},
+ {Name: "expect"},
+ {Name: "expires"},
+ {Name: "from"},
+ {Name: "host"},
+ {Name: "if-match"},
+ {Name: "if-modified-since"},
+ {Name: "if-none-match"},
+ {Name: "if-range"},
+ {Name: "if-unmodified-since"},
+ {Name: "last-modified"},
+ {Name: "link"},
+ {Name: "location"},
+ {Name: "max-forwards"},
+ {Name: "proxy-authenticate"},
+ {Name: "proxy-authorization"},
+ {Name: "range"},
+ {Name: "referer"},
+ {Name: "refresh"},
+ {Name: "retry-after"},
+ {Name: "server"},
+ {Name: "set-cookie"},
+ {Name: "strict-transport-security"},
+ {Name: "transfer-encoding"},
+ {Name: "user-agent"},
+ {Name: "vary"},
+ {Name: "via"},
+ {Name: "www-authenticate"},
+}
+
+type pairNameValue struct {
+ name, value string
+}
+
+type byNameItem struct {
+ name string
+ id uint64
+}
+
+type byNameValueItem struct {
+ pairNameValue
+ id uint64
+}
+
+func headerFieldToString(f hpack.HeaderField) string {
+ return fmt.Sprintf("{Name: \"%s\", Value:\"%s\", Sensitive: %t}", f.Name, f.Value, f.Sensitive)
+}
+
+func pairNameValueToString(v pairNameValue) string {
+ return fmt.Sprintf("{name: \"%s\", value:\"%s\"}", v.name, v.value)
+}
+
+const header = `
+// go generate gen.go
+// Code generated by the command above; DO NOT EDIT.
+
+package hpack
+
+var staticTable = &headerFieldTable{
+ evictCount: 0,
+ byName: map[string]uint64{
+`
+
+//go:generate go run gen.go
+func main() {
+ var bb bytes.Buffer
+ fmt.Fprintf(&bb, header)
+ byName := make(map[string]uint64)
+ byNameValue := make(map[pairNameValue]uint64)
+ for index, entry := range staticTableEntries {
+ id := uint64(index) + 1
+ byName[entry.Name] = id
+ byNameValue[pairNameValue{entry.Name, entry.Value}] = id
+ }
+ // Sort maps for deterministic generation.
+ byNameItems := sortByName(byName)
+ byNameValueItems := sortByNameValue(byNameValue)
+
+ for _, item := range byNameItems {
+ fmt.Fprintf(&bb, "\"%s\":%d,\n", item.name, item.id)
+ }
+ fmt.Fprintf(&bb, "},\n")
+ fmt.Fprintf(&bb, "byNameValue: map[pairNameValue]uint64{\n")
+ for _, item := range byNameValueItems {
+ fmt.Fprintf(&bb, "%s:%d,\n", pairNameValueToString(item.pairNameValue), item.id)
+ }
+ fmt.Fprintf(&bb, "},\n")
+ fmt.Fprintf(&bb, "ents: []HeaderField{\n")
+ for _, value := range staticTableEntries {
+ fmt.Fprintf(&bb, "%s,\n", headerFieldToString(value))
+ }
+ fmt.Fprintf(&bb, "},\n")
+ fmt.Fprintf(&bb, "}\n")
+ genFile("static_table.go", &bb)
+}
+
+func sortByNameValue(byNameValue map[pairNameValue]uint64) []byNameValueItem {
+ var byNameValueItems []byNameValueItem
+ for k, v := range byNameValue {
+ byNameValueItems = append(byNameValueItems, byNameValueItem{k, v})
+ }
+ sort.Slice(byNameValueItems, func(i, j int) bool {
+ return byNameValueItems[i].id < byNameValueItems[j].id
+ })
+ return byNameValueItems
+}
+
+func sortByName(byName map[string]uint64) []byNameItem {
+ var byNameItems []byNameItem
+ for k, v := range byName {
+ byNameItems = append(byNameItems, byNameItem{k, v})
+ }
+ sort.Slice(byNameItems, func(i, j int) bool {
+ return byNameItems[i].id < byNameItems[j].id
+ })
+ return byNameItems
+}
+
+func genFile(name string, buf *bytes.Buffer) {
+ b, err := format.Source(buf.Bytes())
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ if err := ioutil.WriteFile(name, b, 0644); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
diff --git a/http2/hpack/hpack.go b/http2/hpack/hpack.go
index ebdfbee964..7a1d976696 100644
--- a/http2/hpack/hpack.go
+++ b/http2/hpack/hpack.go
@@ -211,7 +211,7 @@ func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
return dt.ents[dt.len()-(int(i)-staticTable.len())], true
}
-// Decode decodes an entire block.
+// DecodeFull decodes an entire block.
//
// TODO: remove this method and make it incremental later? This is
// easier for debugging now.
@@ -359,6 +359,7 @@ func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
var hf HeaderField
wantStr := d.emitEnabled || it.indexed()
+ var undecodedName undecodedString
if nameIdx > 0 {
ihf, ok := d.at(nameIdx)
if !ok {
@@ -366,15 +367,27 @@ func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
}
hf.Name = ihf.Name
} else {
- hf.Name, buf, err = d.readString(buf, wantStr)
+ undecodedName, buf, err = d.readString(buf)
if err != nil {
return err
}
}
- hf.Value, buf, err = d.readString(buf, wantStr)
+ undecodedValue, buf, err := d.readString(buf)
if err != nil {
return err
}
+ if wantStr {
+ if nameIdx <= 0 {
+ hf.Name, err = d.decodeString(undecodedName)
+ if err != nil {
+ return err
+ }
+ }
+ hf.Value, err = d.decodeString(undecodedValue)
+ if err != nil {
+ return err
+ }
+ }
d.buf = buf
if it.indexed() {
d.dynTab.add(hf)
@@ -459,46 +472,52 @@ func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) {
return 0, origP, errNeedMore
}
-// readString decodes an hpack string from p.
+// readString reads an hpack string from p.
//
-// wantStr is whether s will be used. If false, decompression and
-// []byte->string garbage are skipped if s will be ignored
-// anyway. This does mean that huffman decoding errors for non-indexed
-// strings past the MAX_HEADER_LIST_SIZE are ignored, but the server
-// is returning an error anyway, and because they're not indexed, the error
-// won't affect the decoding state.
-func (d *Decoder) readString(p []byte, wantStr bool) (s string, remain []byte, err error) {
+// It returns a reference to the encoded string data to permit deferring decode costs
+// until after the caller verifies all data is present.
+func (d *Decoder) readString(p []byte) (u undecodedString, remain []byte, err error) {
if len(p) == 0 {
- return "", p, errNeedMore
+ return u, p, errNeedMore
}
isHuff := p[0]&128 != 0
strLen, p, err := readVarInt(7, p)
if err != nil {
- return "", p, err
+ return u, p, err
}
if d.maxStrLen != 0 && strLen > uint64(d.maxStrLen) {
- return "", nil, ErrStringLength
+ // Returning an error here means Huffman decoding errors
+ // for non-indexed strings past the maximum string length
+ // are ignored, but the server is returning an error anyway
+ // and because the string is not indexed the error will not
+ // affect the decoding state.
+ return u, nil, ErrStringLength
}
if uint64(len(p)) < strLen {
- return "", p, errNeedMore
- }
- if !isHuff {
- if wantStr {
- s = string(p[:strLen])
- }
- return s, p[strLen:], nil
+ return u, p, errNeedMore
}
+ u.isHuff = isHuff
+ u.b = p[:strLen]
+ return u, p[strLen:], nil
+}
- if wantStr {
- buf := bufPool.Get().(*bytes.Buffer)
- buf.Reset() // don't trust others
- defer bufPool.Put(buf)
- if err := huffmanDecode(buf, d.maxStrLen, p[:strLen]); err != nil {
- buf.Reset()
- return "", nil, err
- }
+type undecodedString struct {
+ isHuff bool
+ b []byte
+}
+
+func (d *Decoder) decodeString(u undecodedString) (string, error) {
+ if !u.isHuff {
+ return string(u.b), nil
+ }
+ buf := bufPool.Get().(*bytes.Buffer)
+ buf.Reset() // don't trust others
+ var s string
+ err := huffmanDecode(buf, d.maxStrLen, u.b)
+ if err == nil {
s = buf.String()
- buf.Reset() // be nice to GC
}
- return s, p[strLen:], nil
+ buf.Reset() // be nice to GC
+ bufPool.Put(buf)
+ return s, err
}
diff --git a/http2/hpack/hpack_test.go b/http2/hpack/hpack_test.go
index f9092e8bb9..b4b2a5d666 100644
--- a/http2/hpack/hpack_test.go
+++ b/http2/hpack/hpack_test.go
@@ -728,6 +728,36 @@ func TestEmitEnabled(t *testing.T) {
}
}
+func TestSlowIncrementalDecode(t *testing.T) {
+ // TODO(dneil): Fix for -race mode.
+ t.Skip("too slow in -race mode")
+
+ var buf bytes.Buffer
+ enc := NewEncoder(&buf)
+ hf := HeaderField{
+ Name: strings.Repeat("k", 1<<20),
+ Value: strings.Repeat("v", 1<<20),
+ }
+ enc.WriteField(hf)
+ hbuf := buf.Bytes()
+ count := 0
+ dec := NewDecoder(initialHeaderTableSize, func(got HeaderField) {
+ count++
+ if count != 1 {
+ t.Errorf("decoded %v fields, want 1", count)
+ }
+ if got.Name != hf.Name {
+ t.Errorf("decoded Name does not match input")
+ }
+ if got.Value != hf.Value {
+ t.Errorf("decoded Value does not match input")
+ }
+ })
+ for i := 0; i < len(hbuf); i++ {
+ dec.Write(hbuf[i : i+1])
+ }
+}
+
func TestSaveBufLimit(t *testing.T) {
const maxStr = 1 << 10
var got []HeaderField
diff --git a/http2/hpack/static_table.go b/http2/hpack/static_table.go
new file mode 100644
index 0000000000..754a1eb919
--- /dev/null
+++ b/http2/hpack/static_table.go
@@ -0,0 +1,188 @@
+// go generate gen.go
+// Code generated by the command above; DO NOT EDIT.
+
+package hpack
+
+var staticTable = &headerFieldTable{
+ evictCount: 0,
+ byName: map[string]uint64{
+ ":authority": 1,
+ ":method": 3,
+ ":path": 5,
+ ":scheme": 7,
+ ":status": 14,
+ "accept-charset": 15,
+ "accept-encoding": 16,
+ "accept-language": 17,
+ "accept-ranges": 18,
+ "accept": 19,
+ "access-control-allow-origin": 20,
+ "age": 21,
+ "allow": 22,
+ "authorization": 23,
+ "cache-control": 24,
+ "content-disposition": 25,
+ "content-encoding": 26,
+ "content-language": 27,
+ "content-length": 28,
+ "content-location": 29,
+ "content-range": 30,
+ "content-type": 31,
+ "cookie": 32,
+ "date": 33,
+ "etag": 34,
+ "expect": 35,
+ "expires": 36,
+ "from": 37,
+ "host": 38,
+ "if-match": 39,
+ "if-modified-since": 40,
+ "if-none-match": 41,
+ "if-range": 42,
+ "if-unmodified-since": 43,
+ "last-modified": 44,
+ "link": 45,
+ "location": 46,
+ "max-forwards": 47,
+ "proxy-authenticate": 48,
+ "proxy-authorization": 49,
+ "range": 50,
+ "referer": 51,
+ "refresh": 52,
+ "retry-after": 53,
+ "server": 54,
+ "set-cookie": 55,
+ "strict-transport-security": 56,
+ "transfer-encoding": 57,
+ "user-agent": 58,
+ "vary": 59,
+ "via": 60,
+ "www-authenticate": 61,
+ },
+ byNameValue: map[pairNameValue]uint64{
+ {name: ":authority", value: ""}: 1,
+ {name: ":method", value: "GET"}: 2,
+ {name: ":method", value: "POST"}: 3,
+ {name: ":path", value: "/"}: 4,
+ {name: ":path", value: "/index.html"}: 5,
+ {name: ":scheme", value: "http"}: 6,
+ {name: ":scheme", value: "https"}: 7,
+ {name: ":status", value: "200"}: 8,
+ {name: ":status", value: "204"}: 9,
+ {name: ":status", value: "206"}: 10,
+ {name: ":status", value: "304"}: 11,
+ {name: ":status", value: "400"}: 12,
+ {name: ":status", value: "404"}: 13,
+ {name: ":status", value: "500"}: 14,
+ {name: "accept-charset", value: ""}: 15,
+ {name: "accept-encoding", value: "gzip, deflate"}: 16,
+ {name: "accept-language", value: ""}: 17,
+ {name: "accept-ranges", value: ""}: 18,
+ {name: "accept", value: ""}: 19,
+ {name: "access-control-allow-origin", value: ""}: 20,
+ {name: "age", value: ""}: 21,
+ {name: "allow", value: ""}: 22,
+ {name: "authorization", value: ""}: 23,
+ {name: "cache-control", value: ""}: 24,
+ {name: "content-disposition", value: ""}: 25,
+ {name: "content-encoding", value: ""}: 26,
+ {name: "content-language", value: ""}: 27,
+ {name: "content-length", value: ""}: 28,
+ {name: "content-location", value: ""}: 29,
+ {name: "content-range", value: ""}: 30,
+ {name: "content-type", value: ""}: 31,
+ {name: "cookie", value: ""}: 32,
+ {name: "date", value: ""}: 33,
+ {name: "etag", value: ""}: 34,
+ {name: "expect", value: ""}: 35,
+ {name: "expires", value: ""}: 36,
+ {name: "from", value: ""}: 37,
+ {name: "host", value: ""}: 38,
+ {name: "if-match", value: ""}: 39,
+ {name: "if-modified-since", value: ""}: 40,
+ {name: "if-none-match", value: ""}: 41,
+ {name: "if-range", value: ""}: 42,
+ {name: "if-unmodified-since", value: ""}: 43,
+ {name: "last-modified", value: ""}: 44,
+ {name: "link", value: ""}: 45,
+ {name: "location", value: ""}: 46,
+ {name: "max-forwards", value: ""}: 47,
+ {name: "proxy-authenticate", value: ""}: 48,
+ {name: "proxy-authorization", value: ""}: 49,
+ {name: "range", value: ""}: 50,
+ {name: "referer", value: ""}: 51,
+ {name: "refresh", value: ""}: 52,
+ {name: "retry-after", value: ""}: 53,
+ {name: "server", value: ""}: 54,
+ {name: "set-cookie", value: ""}: 55,
+ {name: "strict-transport-security", value: ""}: 56,
+ {name: "transfer-encoding", value: ""}: 57,
+ {name: "user-agent", value: ""}: 58,
+ {name: "vary", value: ""}: 59,
+ {name: "via", value: ""}: 60,
+ {name: "www-authenticate", value: ""}: 61,
+ },
+ ents: []HeaderField{
+ {Name: ":authority", Value: "", Sensitive: false},
+ {Name: ":method", Value: "GET", Sensitive: false},
+ {Name: ":method", Value: "POST", Sensitive: false},
+ {Name: ":path", Value: "/", Sensitive: false},
+ {Name: ":path", Value: "/index.html", Sensitive: false},
+ {Name: ":scheme", Value: "http", Sensitive: false},
+ {Name: ":scheme", Value: "https", Sensitive: false},
+ {Name: ":status", Value: "200", Sensitive: false},
+ {Name: ":status", Value: "204", Sensitive: false},
+ {Name: ":status", Value: "206", Sensitive: false},
+ {Name: ":status", Value: "304", Sensitive: false},
+ {Name: ":status", Value: "400", Sensitive: false},
+ {Name: ":status", Value: "404", Sensitive: false},
+ {Name: ":status", Value: "500", Sensitive: false},
+ {Name: "accept-charset", Value: "", Sensitive: false},
+ {Name: "accept-encoding", Value: "gzip, deflate", Sensitive: false},
+ {Name: "accept-language", Value: "", Sensitive: false},
+ {Name: "accept-ranges", Value: "", Sensitive: false},
+ {Name: "accept", Value: "", Sensitive: false},
+ {Name: "access-control-allow-origin", Value: "", Sensitive: false},
+ {Name: "age", Value: "", Sensitive: false},
+ {Name: "allow", Value: "", Sensitive: false},
+ {Name: "authorization", Value: "", Sensitive: false},
+ {Name: "cache-control", Value: "", Sensitive: false},
+ {Name: "content-disposition", Value: "", Sensitive: false},
+ {Name: "content-encoding", Value: "", Sensitive: false},
+ {Name: "content-language", Value: "", Sensitive: false},
+ {Name: "content-length", Value: "", Sensitive: false},
+ {Name: "content-location", Value: "", Sensitive: false},
+ {Name: "content-range", Value: "", Sensitive: false},
+ {Name: "content-type", Value: "", Sensitive: false},
+ {Name: "cookie", Value: "", Sensitive: false},
+ {Name: "date", Value: "", Sensitive: false},
+ {Name: "etag", Value: "", Sensitive: false},
+ {Name: "expect", Value: "", Sensitive: false},
+ {Name: "expires", Value: "", Sensitive: false},
+ {Name: "from", Value: "", Sensitive: false},
+ {Name: "host", Value: "", Sensitive: false},
+ {Name: "if-match", Value: "", Sensitive: false},
+ {Name: "if-modified-since", Value: "", Sensitive: false},
+ {Name: "if-none-match", Value: "", Sensitive: false},
+ {Name: "if-range", Value: "", Sensitive: false},
+ {Name: "if-unmodified-since", Value: "", Sensitive: false},
+ {Name: "last-modified", Value: "", Sensitive: false},
+ {Name: "link", Value: "", Sensitive: false},
+ {Name: "location", Value: "", Sensitive: false},
+ {Name: "max-forwards", Value: "", Sensitive: false},
+ {Name: "proxy-authenticate", Value: "", Sensitive: false},
+ {Name: "proxy-authorization", Value: "", Sensitive: false},
+ {Name: "range", Value: "", Sensitive: false},
+ {Name: "referer", Value: "", Sensitive: false},
+ {Name: "refresh", Value: "", Sensitive: false},
+ {Name: "retry-after", Value: "", Sensitive: false},
+ {Name: "server", Value: "", Sensitive: false},
+ {Name: "set-cookie", Value: "", Sensitive: false},
+ {Name: "strict-transport-security", Value: "", Sensitive: false},
+ {Name: "transfer-encoding", Value: "", Sensitive: false},
+ {Name: "user-agent", Value: "", Sensitive: false},
+ {Name: "vary", Value: "", Sensitive: false},
+ {Name: "via", Value: "", Sensitive: false},
+ {Name: "www-authenticate", Value: "", Sensitive: false},
+ },
+}
diff --git a/http2/hpack/tables.go b/http2/hpack/tables.go
index a66cfbea69..8cbdf3f019 100644
--- a/http2/hpack/tables.go
+++ b/http2/hpack/tables.go
@@ -96,8 +96,7 @@ func (t *headerFieldTable) evictOldest(n int) {
// meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
// table, the return value i actually refers to the entry t.ents[t.len()-i].
//
-// All tables are assumed to be a dynamic tables except for the global
-// staticTable pointer.
+// All tables are assumed to be a dynamic tables except for the global staticTable.
//
// See Section 2.3.3.
func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
@@ -125,81 +124,6 @@ func (t *headerFieldTable) idToIndex(id uint64) uint64 {
return k + 1
}
-// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
-var staticTable = newStaticTable()
-var staticTableEntries = [...]HeaderField{
- {Name: ":authority"},
- {Name: ":method", Value: "GET"},
- {Name: ":method", Value: "POST"},
- {Name: ":path", Value: "/"},
- {Name: ":path", Value: "/index.html"},
- {Name: ":scheme", Value: "http"},
- {Name: ":scheme", Value: "https"},
- {Name: ":status", Value: "200"},
- {Name: ":status", Value: "204"},
- {Name: ":status", Value: "206"},
- {Name: ":status", Value: "304"},
- {Name: ":status", Value: "400"},
- {Name: ":status", Value: "404"},
- {Name: ":status", Value: "500"},
- {Name: "accept-charset"},
- {Name: "accept-encoding", Value: "gzip, deflate"},
- {Name: "accept-language"},
- {Name: "accept-ranges"},
- {Name: "accept"},
- {Name: "access-control-allow-origin"},
- {Name: "age"},
- {Name: "allow"},
- {Name: "authorization"},
- {Name: "cache-control"},
- {Name: "content-disposition"},
- {Name: "content-encoding"},
- {Name: "content-language"},
- {Name: "content-length"},
- {Name: "content-location"},
- {Name: "content-range"},
- {Name: "content-type"},
- {Name: "cookie"},
- {Name: "date"},
- {Name: "etag"},
- {Name: "expect"},
- {Name: "expires"},
- {Name: "from"},
- {Name: "host"},
- {Name: "if-match"},
- {Name: "if-modified-since"},
- {Name: "if-none-match"},
- {Name: "if-range"},
- {Name: "if-unmodified-since"},
- {Name: "last-modified"},
- {Name: "link"},
- {Name: "location"},
- {Name: "max-forwards"},
- {Name: "proxy-authenticate"},
- {Name: "proxy-authorization"},
- {Name: "range"},
- {Name: "referer"},
- {Name: "refresh"},
- {Name: "retry-after"},
- {Name: "server"},
- {Name: "set-cookie"},
- {Name: "strict-transport-security"},
- {Name: "transfer-encoding"},
- {Name: "user-agent"},
- {Name: "vary"},
- {Name: "via"},
- {Name: "www-authenticate"},
-}
-
-func newStaticTable() *headerFieldTable {
- t := &headerFieldTable{}
- t.init()
- for _, e := range staticTableEntries[:] {
- t.addEntry(e)
- }
- return t
-}
-
var huffmanCodes = [256]uint32{
0x1ff8,
0x7fffd8,
diff --git a/http2/hpack/tables_test.go b/http2/hpack/tables_test.go
index d963f36354..66366a1e15 100644
--- a/http2/hpack/tables_test.go
+++ b/http2/hpack/tables_test.go
@@ -207,6 +207,12 @@ func TestStaticTable(t *testing.T) {
if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
t.Errorf("header index %d value = %q; want %q", i, got, want)
}
+ if got, want := staticTable.ents[i-1].Sensitive, false; got != want {
+ t.Errorf("header index %d sensitive = %t; want %t", i, got, want)
+ }
+ if got, want := strconv.Itoa(int(staticTable.byNameValue[pairNameValue{name: m[2], value: m[3]}])), m[1]; got != want {
+ t.Errorf("header by name %s value %s index = %s; want %s", m[2], m[3], got, want)
+ }
}
if err := bs.Err(); err != nil {
t.Error(err)
diff --git a/http2/server.go b/http2/server.go
index 43cc2a34ad..8cb14f3c97 100644
--- a/http2/server.go
+++ b/http2/server.go
@@ -98,6 +98,19 @@ type Server struct {
// the HTTP/2 spec's recommendations.
MaxConcurrentStreams uint32
+ // MaxDecoderHeaderTableSize optionally specifies the http2
+ // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+ // informs the remote endpoint of the maximum size of the header compression
+ // table used to decode header blocks, in octets. If zero, the default value
+ // of 4096 is used.
+ MaxDecoderHeaderTableSize uint32
+
+ // MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+ // header compression table used for encoding request headers. Received
+ // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+ // the default value of 4096 is used.
+ MaxEncoderHeaderTableSize uint32
+
// MaxReadFrameSize optionally specifies the largest frame
// this server is willing to read. A valid value is between
// 16k and 16M, inclusive. If zero or otherwise invalid, a
@@ -170,6 +183,20 @@ func (s *Server) maxConcurrentStreams() uint32 {
return defaultMaxStreams
}
+func (s *Server) maxDecoderHeaderTableSize() uint32 {
+ if v := s.MaxDecoderHeaderTableSize; v > 0 {
+ return v
+ }
+ return initialHeaderTableSize
+}
+
+func (s *Server) maxEncoderHeaderTableSize() uint32 {
+ if v := s.MaxEncoderHeaderTableSize; v > 0 {
+ return v
+ }
+ return initialHeaderTableSize
+}
+
// maxQueuedControlFrames is the maximum number of control frames like
// SETTINGS, PING and RST_STREAM that will be queued for writing before
// the connection is closed to prevent memory exhaustion attacks.
@@ -394,7 +421,6 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
advMaxStreams: s.maxConcurrentStreams(),
initialStreamSendWindowSize: initialWindowSize,
maxFrameSize: initialMaxFrameSize,
- headerTableSize: initialHeaderTableSize,
serveG: newGoroutineLock(),
pushEnabled: true,
sawClientPreface: opts.SawClientPreface,
@@ -422,14 +448,15 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
// configured value for inflow, that will be updated when we send a
// WINDOW_UPDATE shortly after sending SETTINGS.
sc.flow.add(initialWindowSize)
- sc.inflow.add(initialWindowSize)
+ sc.inflow.init(initialWindowSize)
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
+ sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
fr := NewFramer(sc.bw, c)
if s.CountError != nil {
fr.countError = s.CountError
}
- fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+ fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
fr.MaxHeaderListSize = sc.maxHeaderListSize()
fr.SetMaxReadFrameSize(s.maxReadFrameSize())
sc.framer = fr
@@ -536,8 +563,8 @@ type serverConn struct {
wroteFrameCh chan frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes
bodyReadCh chan bodyReadMsg // from handlers -> serve
serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop
- flow flow // conn-wide (not stream-specific) outbound flow control
- inflow flow // conn-wide inbound flow control
+ flow outflow // conn-wide (not stream-specific) outbound flow control
+ inflow inflow // conn-wide inbound flow control
tlsState *tls.ConnectionState // shared by all handlers, like net/http
remoteAddrStr string
writeSched WriteScheduler
@@ -559,9 +586,9 @@ type serverConn struct {
streams map[uint32]*stream
initialStreamSendWindowSize int32
maxFrameSize int32
- headerTableSize uint32
peerMaxHeaderListSize uint32 // zero means unknown (default)
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
+ canonHeaderKeysSize int // canonHeader keys size in bytes
writingFrame bool // started writing a frame (on serve goroutine or separate)
writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh
needsFrameFlush bool // last frame write wasn't a flush
@@ -614,15 +641,17 @@ type stream struct {
cancelCtx func()
// owned by serverConn's serve loop:
- bodyBytes int64 // body bytes seen so far
- declBodyBytes int64 // or -1 if undeclared
- flow flow // limits writing from Handler to client
- inflow flow // what the client is allowed to POST/etc to us
+ bodyBytes int64 // body bytes seen so far
+ declBodyBytes int64 // or -1 if undeclared
+ flow outflow // limits writing from Handler to client
+ inflow inflow // what the client is allowed to POST/etc to us
state streamState
resetQueued bool // RST_STREAM queued for write; set by sc.resetStream
gotTrailerHeader bool // HEADER frame for trailers was seen
wroteHeaders bool // whether we wrote headers (not status 100)
+ readDeadline *time.Timer // nil if unused
writeDeadline *time.Timer // nil if unused
+ closeErr error // set before cw is closed
trailer http.Header // accumulated trailers
reqTrailer http.Header // handler's Request.Trailer
@@ -738,6 +767,13 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
}
}
+// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size
+// of the entries in the canonHeader cache.
+// This should be larger than the size of unique, uncommon header keys likely to
+// be sent by the peer, while not so high as to permit unreasonable memory usage
+// if the peer sends an unbounded number of unique header keys.
+const maxCachedCanonicalHeadersKeysSize = 2048
+
func (sc *serverConn) canonicalHeader(v string) string {
sc.serveG.check()
buildCommonHeaderMapsOnce()
@@ -753,14 +789,10 @@ func (sc *serverConn) canonicalHeader(v string) string {
sc.canonHeader = make(map[string]string)
}
cv = http.CanonicalHeaderKey(v)
- // maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
- // entries in the canonHeader cache. This should be larger than the number
- // of unique, uncommon header keys likely to be sent by the peer, while not
- // so high as to permit unreasonable memory usage if the peer sends an unbounded
- // number of unique header keys.
- const maxCachedCanonicalHeaders = 32
- if len(sc.canonHeader) < maxCachedCanonicalHeaders {
+ size := 100 + len(v)*2 // 100 bytes of map overhead + key + value
+ if sc.canonHeaderKeysSize+size <= maxCachedCanonicalHeadersKeysSize {
sc.canonHeader[v] = cv
+ sc.canonHeaderKeysSize += size
}
return cv
}
@@ -811,8 +843,13 @@ type frameWriteResult struct {
// and then reports when it's done.
// At most one goroutine can be running writeFrameAsync at a time per
// serverConn.
-func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest) {
- err := wr.write.writeFrame(sc)
+func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest, wd *writeData) {
+ var err error
+ if wd == nil {
+ err = wr.write.writeFrame(sc)
+ } else {
+ err = sc.framer.endWrite()
+ }
sc.wroteFrameCh <- frameWriteResult{wr: wr, err: err}
}
@@ -862,6 +899,7 @@ func (sc *serverConn) serve() {
{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
{SettingMaxConcurrentStreams, sc.advMaxStreams},
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
+ {SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
},
})
@@ -869,7 +907,9 @@ func (sc *serverConn) serve() {
// Each connection starts with initialWindowSize inflow tokens.
// If a higher value is configured, we add more tokens.
- sc.sendWindowUpdate(nil)
+ if diff := sc.srv.initialConnRecvWindowSize() - initialWindowSize; diff > 0 {
+ sc.sendWindowUpdate(nil, int(diff))
+ }
if err := sc.readPreface(); err != nil {
sc.condlogf(err, "http2: server: error reading preface from client %v: %v", sc.conn.RemoteAddr(), err)
@@ -946,6 +986,8 @@ func (sc *serverConn) serve() {
}
case *startPushRequest:
sc.startPush(v)
+ case func(*serverConn):
+ v(sc)
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
@@ -1214,9 +1256,16 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) {
sc.writingFrameAsync = false
err := wr.write.writeFrame(sc)
sc.wroteFrame(frameWriteResult{wr: wr, err: err})
+ } else if wd, ok := wr.write.(*writeData); ok {
+ // Encode the frame in the serve goroutine, to ensure we don't have
+ // any lingering asynchronous references to data passed to Write.
+ // See https://go.dev/issue/58446.
+ sc.framer.startWriteDataPadded(wd.streamID, wd.endStream, wd.p, nil)
+ sc.writingFrameAsync = true
+ go sc.writeFrameAsync(wr, wd)
} else {
sc.writingFrameAsync = true
- go sc.writeFrameAsync(wr)
+ go sc.writeFrameAsync(wr, nil)
}
}
@@ -1459,6 +1508,21 @@ func (sc *serverConn) processFrame(f Frame) error {
sc.sawFirstSettings = true
}
+ // Discard frames for streams initiated after the identified last
+ // stream sent in a GOAWAY, or all frames after sending an error.
+ // We still need to return connection-level flow control for DATA frames.
+ // RFC 9113 Section 6.8.
+ if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || f.Header().StreamID > sc.maxClientStreamID) {
+
+ if f, ok := f.(*DataFrame); ok {
+ if !sc.inflow.take(f.Length) {
+ return sc.countError("data_flow", streamError(f.Header().StreamID, ErrCodeFlowControl))
+ }
+ sc.sendWindowUpdate(nil, int(f.Length)) // conn-level
+ }
+ return nil
+ }
+
switch f := f.(type) {
case *SettingsFrame:
return sc.processSettings(f)
@@ -1501,9 +1565,6 @@ func (sc *serverConn) processPing(f *PingFrame) error {
// PROTOCOL_ERROR."
return sc.countError("ping_on_stream", ConnectionError(ErrCodeProtocol))
}
- if sc.inGoAway && sc.goAwayCode != ErrCodeNo {
- return nil
- }
sc.writeFrame(FrameWriteRequest{write: writePingAck{f}})
return nil
}
@@ -1565,6 +1626,9 @@ func (sc *serverConn) closeStream(st *stream, err error) {
panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state))
}
st.state = stateClosed
+ if st.readDeadline != nil {
+ st.readDeadline.Stop()
+ }
if st.writeDeadline != nil {
st.writeDeadline.Stop()
}
@@ -1586,10 +1650,18 @@ func (sc *serverConn) closeStream(st *stream, err error) {
if p := st.body; p != nil {
// Return any buffered unread bytes worth of conn-level flow control.
// See golang.org/issue/16481
- sc.sendWindowUpdate(nil)
+ sc.sendWindowUpdate(nil, p.Len())
p.CloseWithError(err)
}
+ if e, ok := err.(StreamError); ok {
+ if e.Cause != nil {
+ err = e.Cause
+ } else {
+ err = errStreamClosed
+ }
+ }
+ st.closeErr = err
st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc
sc.writeSched.CloseStream(st.id)
}
@@ -1632,7 +1704,6 @@ func (sc *serverConn) processSetting(s Setting) error {
}
switch s.ID {
case SettingHeaderTableSize:
- sc.headerTableSize = s.Val
sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
case SettingEnablePush:
sc.pushEnabled = s.Val != 0
@@ -1686,16 +1757,6 @@ func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
func (sc *serverConn) processData(f *DataFrame) error {
sc.serveG.check()
id := f.Header().StreamID
- if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || id > sc.maxClientStreamID) {
- // Discard all DATA frames if the GOAWAY is due to an
- // error, or:
- //
- // Section 6.8: After sending a GOAWAY frame, the sender
- // can discard frames for streams initiated by the
- // receiver with identifiers higher than the identified
- // last stream.
- return nil
- }
data := f.Data()
state, st := sc.state(id)
@@ -1726,15 +1787,10 @@ func (sc *serverConn) processData(f *DataFrame) error {
// But still enforce their connection-level flow control,
// and return any flow control bytes since we're not going
// to consume them.
- if sc.inflow.available() < int32(f.Length) {
+ if !sc.inflow.take(f.Length) {
return sc.countError("data_flow", streamError(id, ErrCodeFlowControl))
}
- // Deduct the flow control from inflow, since we're
- // going to immediately add it back in
- // sendWindowUpdate, which also schedules sending the
- // frames.
- sc.inflow.take(int32(f.Length))
- sc.sendWindowUpdate(nil) // conn-level
+ sc.sendWindowUpdate(nil, int(f.Length)) // conn-level
if st != nil && st.resetQueued {
// Already have a stream error in flight. Don't send another.
@@ -1748,11 +1804,10 @@ func (sc *serverConn) processData(f *DataFrame) error {
// Sender sending more than they'd declared?
if st.declBodyBytes != -1 && st.bodyBytes+int64(len(data)) > st.declBodyBytes {
- if sc.inflow.available() < int32(f.Length) {
+ if !sc.inflow.take(f.Length) {
return sc.countError("data_flow", streamError(id, ErrCodeFlowControl))
}
- sc.inflow.take(int32(f.Length))
- sc.sendWindowUpdate(nil) // conn-level
+ sc.sendWindowUpdate(nil, int(f.Length)) // conn-level
st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes))
// RFC 7540, sec 8.1.2.6: A request or response is also malformed if the
@@ -1762,15 +1817,14 @@ func (sc *serverConn) processData(f *DataFrame) error {
}
if f.Length > 0 {
// Check whether the client has flow control quota.
- if st.inflow.available() < int32(f.Length) {
+ if !takeInflows(&sc.inflow, &st.inflow, f.Length) {
return sc.countError("flow_on_data_length", streamError(id, ErrCodeFlowControl))
}
- st.inflow.take(int32(f.Length))
if len(data) > 0 {
wrote, err := st.body.Write(data)
if err != nil {
- sc.sendWindowUpdate32(nil, int32(f.Length)-int32(wrote))
+ sc.sendWindowUpdate(nil, int(f.Length)-wrote)
return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed))
}
if wrote != len(data) {
@@ -1781,10 +1835,12 @@ func (sc *serverConn) processData(f *DataFrame) error {
// Return any padded flow control now, since we won't
// refund it later on body reads.
- if pad := int32(f.Length) - int32(len(data)); pad > 0 {
- sc.sendWindowUpdate32(nil, pad)
- sc.sendWindowUpdate32(st, pad)
- }
+ // Call sendWindowUpdate even if there is no padding,
+ // to return buffered flow control credit if the sent
+ // window has shrunk.
+ pad := int32(f.Length) - int32(len(data))
+ sc.sendWindowUpdate32(nil, pad)
+ sc.sendWindowUpdate32(st, pad)
}
if f.StreamEnded() {
st.endStream()
@@ -1838,19 +1894,27 @@ func (st *stream) copyTrailersToHandlerRequest() {
}
}
+// onReadTimeout is run on its own goroutine (from time.AfterFunc)
+// when the stream's ReadTimeout has fired.
+func (st *stream) onReadTimeout() {
+ // Wrap the ErrDeadlineExceeded to avoid callers depending on us
+ // returning the bare error.
+ st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded))
+}
+
// onWriteTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's WriteTimeout has fired.
func (st *stream) onWriteTimeout() {
- st.sc.writeFrameFromHandler(FrameWriteRequest{write: streamError(st.id, ErrCodeInternal)})
+ st.sc.writeFrameFromHandler(FrameWriteRequest{write: StreamError{
+ StreamID: st.id,
+ Code: ErrCodeInternal,
+ Cause: os.ErrDeadlineExceeded,
+ }})
}
func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
sc.serveG.check()
id := f.StreamID
- if sc.inGoAway {
- // Ignore.
- return nil
- }
// http://tools.ietf.org/html/rfc7540#section-5.1.1
// Streams initiated by a client MUST use odd-numbered stream
// identifiers. [...] An endpoint that receives an unexpected
@@ -1953,6 +2017,9 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
// (in Go 1.8), though. That's a more sane option anyway.
if sc.hs.ReadTimeout != 0 {
sc.conn.SetReadDeadline(time.Time{})
+ if st.body != nil {
+ st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
+ }
}
go sc.runHandler(rw, req, handler)
@@ -2021,9 +2088,6 @@ func (sc *serverConn) checkPriority(streamID uint32, p PriorityParam) error {
}
func (sc *serverConn) processPriority(f *PriorityFrame) error {
- if sc.inGoAway {
- return nil
- }
if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil {
return err
}
@@ -2048,8 +2112,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
st.cw.Init()
st.flow.conn = &sc.flow // link to conn-level counter
st.flow.add(sc.initialStreamSendWindowSize)
- st.inflow.conn = &sc.inflow // link to conn-level counter
- st.inflow.add(sc.srv.initialStreamRecvWindowSize())
+ st.inflow.init(sc.srv.initialStreamRecvWindowSize())
if sc.hs.WriteTimeout != 0 {
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
}
@@ -2141,7 +2204,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
tlsState = sc.tlsState
}
- needsContinue := rp.header.Get("Expect") == "100-continue"
+ needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
if needsContinue {
rp.header.Del("Expect")
}
@@ -2322,71 +2385,37 @@ func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int, err error) {
func (sc *serverConn) noteBodyRead(st *stream, n int) {
sc.serveG.check()
- sc.sendWindowUpdate(nil) // conn-level
+ sc.sendWindowUpdate(nil, n) // conn-level
if st.state != stateHalfClosedRemote && st.state != stateClosed {
// Don't send this WINDOW_UPDATE if the stream is closed
// remotely.
- sc.sendWindowUpdate(st)
+ sc.sendWindowUpdate(st, n)
}
}
// st may be nil for conn-level
-func (sc *serverConn) sendWindowUpdate(st *stream) {
- sc.serveG.check()
-
- var n int32
- if st == nil {
- if avail, windowSize := sc.inflow.available(), sc.srv.initialConnRecvWindowSize(); avail > windowSize/2 {
- return
- } else {
- n = windowSize - avail
- }
- } else {
- if avail, windowSize := st.inflow.available(), sc.srv.initialStreamRecvWindowSize(); avail > windowSize/2 {
- return
- } else {
- n = windowSize - avail
- }
- }
- // "The legal range for the increment to the flow control
- // window is 1 to 2^31-1 (2,147,483,647) octets."
- // A Go Read call on 64-bit machines could in theory read
- // a larger Read than this. Very unlikely, but we handle it here
- // rather than elsewhere for now.
- const maxUint31 = 1<<31 - 1
- for n >= maxUint31 {
- sc.sendWindowUpdate32(st, maxUint31)
- n -= maxUint31
- }
- sc.sendWindowUpdate32(st, int32(n))
+func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
+ sc.sendWindowUpdate(st, int(n))
}
// st may be nil for conn-level
-func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
+func (sc *serverConn) sendWindowUpdate(st *stream, n int) {
sc.serveG.check()
- if n == 0 {
- return
- }
- if n < 0 {
- panic("negative update")
- }
var streamID uint32
- if st != nil {
+ var send int32
+ if st == nil {
+ send = sc.inflow.add(n)
+ } else {
streamID = st.id
+ send = st.inflow.add(n)
+ }
+ if send == 0 {
+ return
}
sc.writeFrame(FrameWriteRequest{
- write: writeWindowUpdate{streamID: streamID, n: uint32(n)},
+ write: writeWindowUpdate{streamID: streamID, n: uint32(send)},
stream: st,
})
- var ok bool
- if st == nil {
- ok = sc.inflow.add(n)
- } else {
- ok = st.inflow.add(n)
- }
- if !ok {
- panic("internal error; sent too many window updates without decrements?")
- }
}
// requestBody is the Handler's Request.Body type.
@@ -2474,7 +2503,15 @@ type responseWriterState struct {
type chunkWriter struct{ rws *responseWriterState }
-func (cw chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) }
+func (cw chunkWriter) Write(p []byte) (n int, err error) {
+ n, err = cw.rws.writeChunk(p)
+ if err == errStreamClosed {
+ // If writing failed because the stream has been closed,
+ // return the reason it was closed.
+ err = cw.rws.stream.closeErr
+ }
+ return n, err
+}
func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) > 0 }
@@ -2668,23 +2705,85 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
}
}
+func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
+ st := w.rws.stream
+ if !deadline.IsZero() && deadline.Before(time.Now()) {
+ // If we're setting a deadline in the past, reset the stream immediately
+ // so writes after SetWriteDeadline returns will fail.
+ st.onReadTimeout()
+ return nil
+ }
+ w.rws.conn.sendServeMsg(func(sc *serverConn) {
+ if st.readDeadline != nil {
+ if !st.readDeadline.Stop() {
+ // Deadline already exceeded, or stream has been closed.
+ return
+ }
+ }
+ if deadline.IsZero() {
+ st.readDeadline = nil
+ } else if st.readDeadline == nil {
+ st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout)
+ } else {
+ st.readDeadline.Reset(deadline.Sub(time.Now()))
+ }
+ })
+ return nil
+}
+
+func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
+ st := w.rws.stream
+ if !deadline.IsZero() && deadline.Before(time.Now()) {
+ // If we're setting a deadline in the past, reset the stream immediately
+ // so writes after SetWriteDeadline returns will fail.
+ st.onWriteTimeout()
+ return nil
+ }
+ w.rws.conn.sendServeMsg(func(sc *serverConn) {
+ if st.writeDeadline != nil {
+ if !st.writeDeadline.Stop() {
+ // Deadline already exceeded, or stream has been closed.
+ return
+ }
+ }
+ if deadline.IsZero() {
+ st.writeDeadline = nil
+ } else if st.writeDeadline == nil {
+ st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout)
+ } else {
+ st.writeDeadline.Reset(deadline.Sub(time.Now()))
+ }
+ })
+ return nil
+}
+
func (w *responseWriter) Flush() {
+ w.FlushError()
+}
+
+func (w *responseWriter) FlushError() error {
rws := w.rws
if rws == nil {
panic("Header called after Handler finished")
}
+ var err error
if rws.bw.Buffered() > 0 {
- if err := rws.bw.Flush(); err != nil {
- // Ignore the error. The frame writer already knows.
- return
- }
+ err = rws.bw.Flush()
} else {
// The bufio.Writer won't call chunkWriter.Write
// (writeChunk with zero bytes, so we have to do it
// ourselves to force the HTTP response header and/or
// final DATA frame (with END_STREAM) to be sent.
- rws.writeChunk(nil)
+ _, err = chunkWriter{rws}.Write(nil)
+ if err == nil {
+ select {
+ case <-rws.stream.cw:
+ err = rws.stream.closeErr
+ default:
+ }
+ }
}
+ return err
}
func (w *responseWriter) CloseNotify() <-chan bool {
diff --git a/http2/server_test.go b/http2/server_test.go
index 808b3d13f9..d32b2d85bd 100644
--- a/http2/server_test.go
+++ b/http2/server_test.go
@@ -315,7 +315,7 @@ func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error
if f.FrameHeader.StreamID != 0 {
st.t.Fatalf("WindowUpdate StreamID = %d; want 0", f.FrameHeader.StreamID)
}
- incr := uint32((&Server{}).initialConnRecvWindowSize() - initialWindowSize)
+ incr := uint32(st.sc.srv.initialConnRecvWindowSize() - initialWindowSize)
if f.Increment != incr {
st.t.Fatalf("WindowUpdate increment = %d; want %d", f.Increment, incr)
}
@@ -482,6 +482,22 @@ func (st *serverTester) writeDataPadded(streamID uint32, endStream bool, data, p
}
}
+// writeReadPing sends a PING and immediately reads the PING ACK.
+// It will fail if any other unread data was pending on the connection.
+func (st *serverTester) writeReadPing() {
+ data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
+ if err := st.fr.WritePing(false, data); err != nil {
+ st.t.Fatalf("Error writing PING: %v", err)
+ }
+ p := st.wantPing()
+ if p.Flags&FlagPingAck == 0 {
+ st.t.Fatalf("got a PING, want a PING ACK")
+ }
+ if p.Data != data {
+ st.t.Fatalf("got PING data = %x, want %x", p.Data, data)
+ }
+}
+
func (st *serverTester) readFrame() (Frame, error) {
return st.fr.ReadFrame()
}
@@ -592,6 +608,28 @@ func (st *serverTester) wantWindowUpdate(streamID, incr uint32) {
}
}
+func (st *serverTester) wantFlowControlConsumed(streamID, consumed int32) {
+ var initial int32
+ if streamID == 0 {
+ initial = st.sc.srv.initialConnRecvWindowSize()
+ } else {
+ initial = st.sc.srv.initialStreamRecvWindowSize()
+ }
+ donec := make(chan struct{})
+ st.sc.sendServeMsg(func(sc *serverConn) {
+ defer close(donec)
+ var avail int32
+ if streamID == 0 {
+ avail = sc.inflow.avail + sc.inflow.unsent
+ } else {
+ }
+ if got, want := initial-avail, consumed; got != want {
+ st.t.Errorf("stream %v flow control consumed: %v, want %v", streamID, got, want)
+ }
+ })
+ <-donec
+}
+
func (st *serverTester) wantSettingsAck() {
f, err := st.readFrame()
if err != nil {
@@ -809,6 +847,10 @@ func TestServer_Request_Post_Body_ContentLength_TooSmall(t *testing.T) {
EndHeaders: true,
})
st.writeData(1, true, []byte("12345"))
+ // Return flow control bytes back, since the data handler closed
+ // the stream.
+ st.wantRSTStream(1, ErrCodeProtocol)
+ st.wantFlowControlConsumed(0, 0)
})
}
@@ -1235,95 +1277,89 @@ func TestServer_RejectsLargeFrames(t *testing.T) {
}
func TestServer_Handler_Sends_WindowUpdate(t *testing.T) {
- puppet := newHandlerPuppet()
- st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
- puppet.act(w, r)
- })
- defer st.Close()
- defer puppet.done()
-
- st.greet()
-
- st.writeHeaders(HeadersFrameParam{
- StreamID: 1, // clients send odd numbers
- BlockFragment: st.encodeHeader(":method", "POST"),
- EndStream: false, // data coming
- EndHeaders: true,
- })
- updateSize := 1 << 20 / 2 // the conn & stream size before a WindowUpdate
- st.writeData(1, false, bytes.Repeat([]byte("a"), updateSize-10))
- st.writeData(1, false, bytes.Repeat([]byte("b"), 10))
- puppet.do(readBodyHandler(t, strings.Repeat("a", updateSize-10)))
- puppet.do(readBodyHandler(t, strings.Repeat("b", 10)))
-
- st.wantWindowUpdate(0, uint32(updateSize))
- st.wantWindowUpdate(1, uint32(updateSize))
-
- st.writeData(1, false, bytes.Repeat([]byte("a"), updateSize-10))
- st.writeData(1, true, bytes.Repeat([]byte("c"), 15)) // END_STREAM here
- puppet.do(readBodyHandler(t, strings.Repeat("a", updateSize-10)))
- puppet.do(readBodyHandler(t, strings.Repeat("c", 15)))
-
- st.wantWindowUpdate(0, uint32(updateSize+5))
-}
-
-func TestServer_Handler_Sends_WindowUpdate_SmallStream(t *testing.T) {
+ // Need to set this to at least twice the initial window size,
+ // or st.greet gets stuck waiting for a WINDOW_UPDATE.
+ //
+ // This also needs to be less than MAX_FRAME_SIZE.
+ const windowSize = 65535 * 2
puppet := newHandlerPuppet()
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
puppet.act(w, r)
}, func(s *Server) {
- s.MaxUploadBufferPerStream = 6
+ s.MaxUploadBufferPerConnection = windowSize
+ s.MaxUploadBufferPerStream = windowSize
})
defer st.Close()
defer puppet.done()
st.greet()
-
st.writeHeaders(HeadersFrameParam{
StreamID: 1, // clients send odd numbers
BlockFragment: st.encodeHeader(":method", "POST"),
EndStream: false, // data coming
EndHeaders: true,
})
- st.writeData(1, false, []byte("abcdef"))
- puppet.do(readBodyHandler(t, "abc"))
- puppet.do(readBodyHandler(t, "d"))
- puppet.do(readBodyHandler(t, "ef"))
-
- st.wantWindowUpdate(1, 6)
-
- st.writeData(1, true, []byte("ghijkl")) // END_STREAM here
- puppet.do(readBodyHandler(t, "ghi"))
- puppet.do(readBodyHandler(t, "jkl"))
+ st.writeReadPing()
+
+ // Write less than half the max window of data and consume it.
+ // The server doesn't return flow control yet, buffering the 1024 bytes to
+ // combine with a future update.
+ data := make([]byte, windowSize)
+ st.writeData(1, false, data[:1024])
+ puppet.do(readBodyHandler(t, string(data[:1024])))
+ st.writeReadPing()
+
+ // Write up to the window limit.
+ // The server returns the buffered credit.
+ st.writeData(1, false, data[1024:])
+ st.wantWindowUpdate(0, 1024)
+ st.wantWindowUpdate(1, 1024)
+ st.writeReadPing()
+
+ // The handler consumes the data and the server returns credit.
+ puppet.do(readBodyHandler(t, string(data[1024:])))
+ st.wantWindowUpdate(0, windowSize-1024)
+ st.wantWindowUpdate(1, windowSize-1024)
+ st.writeReadPing()
}
// the version of the TestServer_Handler_Sends_WindowUpdate with padding.
// See golang.org/issue/16556
func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) {
+ const windowSize = 65535 * 2
puppet := newHandlerPuppet()
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
puppet.act(w, r)
+ }, func(s *Server) {
+ s.MaxUploadBufferPerConnection = windowSize
+ s.MaxUploadBufferPerStream = windowSize
})
defer st.Close()
defer puppet.done()
st.greet()
-
st.writeHeaders(HeadersFrameParam{
StreamID: 1,
BlockFragment: st.encodeHeader(":method", "POST"),
EndStream: false,
EndHeaders: true,
})
- st.writeDataPadded(1, false, []byte("abcdef"), []byte{0, 0, 0, 0})
+ st.writeReadPing()
- // Expect to immediately get our 5 bytes of padding back for
- // both the connection and stream (4 bytes of padding + 1 byte of length)
- st.wantWindowUpdate(0, 5)
- st.wantWindowUpdate(1, 5)
+ // Write half a window of data, with some padding.
+ // The server doesn't return the padding yet, buffering the 5 bytes to combine
+ // with a future update.
+ data := make([]byte, windowSize/2)
+ pad := make([]byte, 4)
+ st.writeDataPadded(1, false, data, pad)
+ st.writeReadPing()
- puppet.do(readBodyHandler(t, "abc"))
- puppet.do(readBodyHandler(t, "def"))
+ // The handler consumes the body.
+ // The server returns flow control for the body and padding
+ // (4 bytes of padding + 1 byte of length).
+ puppet.do(readBodyHandler(t, string(data)))
+ st.wantWindowUpdate(0, uint32(len(data)+1+len(pad)))
+ st.wantWindowUpdate(1, uint32(len(data)+1+len(pad)))
}
func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) {
@@ -1668,7 +1704,7 @@ func TestServer_Rejects_HeadersSelfDependence(t *testing.T) {
})
}
-// No PRIORTY frame with a self-dependence.
+// No PRIORITY frame with a self-dependence.
func TestServer_Rejects_PrioritySelfDependence(t *testing.T) {
testServerRejectsStream(t, ErrCodeProtocol, func(st *serverTester) {
st.fr.AllowIllegalWrites = true
@@ -2296,7 +2332,7 @@ func TestServer_Response_Automatic100Continue(t *testing.T) {
}, func(st *serverTester) {
st.writeHeaders(HeadersFrameParam{
StreamID: 1, // clients send odd numbers
- BlockFragment: st.encodeHeader(":method", "POST", "expect", "100-continue"),
+ BlockFragment: st.encodeHeader(":method", "POST", "expect", "100-Continue"),
EndStream: false,
EndHeaders: true,
})
@@ -2512,6 +2548,10 @@ func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) {
// first.
st.wantRSTStream(1, ErrCodeStreamClosed)
+ // We should have our flow control bytes back,
+ // since the handler didn't get them.
+ st.wantFlowControlConsumed(0, 0)
+
// Set up a bunch of machinery to record the panic we saw
// previously.
var (
@@ -2754,6 +2794,43 @@ func TestServerWithH2Load(t *testing.T) {
}
}
+func TestServer_MaxDecoderHeaderTableSize(t *testing.T) {
+ wantHeaderTableSize := uint32(initialHeaderTableSize * 2)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
+ s.MaxDecoderHeaderTableSize = wantHeaderTableSize
+ })
+ defer st.Close()
+
+ var advHeaderTableSize *uint32
+ st.greetAndCheckSettings(func(s Setting) error {
+ switch s.ID {
+ case SettingHeaderTableSize:
+ advHeaderTableSize = &s.Val
+ }
+ return nil
+ })
+
+ if advHeaderTableSize == nil {
+ t.Errorf("server didn't advertise a header table size")
+ } else if got, want := *advHeaderTableSize, wantHeaderTableSize; got != want {
+ t.Errorf("server advertised a header table size of %d, want %d", got, want)
+ }
+}
+
+func TestServer_MaxEncoderHeaderTableSize(t *testing.T) {
+ wantHeaderTableSize := uint32(initialHeaderTableSize / 2)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
+ s.MaxEncoderHeaderTableSize = wantHeaderTableSize
+ })
+ defer st.Close()
+
+ st.greet()
+
+ if got, want := st.sc.hpackEncoder.MaxDynamicTableSize(), wantHeaderTableSize; got != want {
+ t.Errorf("server encoder is using a header table size of %d, want %d", got, want)
+ }
+}
+
// Issue 12843
func TestServerDoS_MaxHeaderListSize(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {})
@@ -3949,6 +4026,7 @@ func TestServer_Rejects_TooSmall(t *testing.T) {
})
st.writeData(1, true, []byte("12345"))
st.wantRSTStream(1, ErrCodeProtocol)
+ st.wantFlowControlConsumed(0, 0)
})
}
@@ -3956,7 +4034,6 @@ func TestServer_Rejects_TooSmall(t *testing.T) {
// and the connection still completing.
func TestServerHandlerConnectionClose(t *testing.T) {
unblockHandler := make(chan bool, 1)
- defer close(unblockHandler) // backup; in case of errors
testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Connection", "close")
w.Header().Set("Foo", "bar")
@@ -3964,6 +4041,7 @@ func TestServerHandlerConnectionClose(t *testing.T) {
<-unblockHandler
return nil
}, func(st *serverTester) {
+ defer close(unblockHandler) // backup; in case of errors
st.writeHeaders(HeadersFrameParam{
StreamID: 1,
BlockFragment: st.encodeHeader(),
@@ -3972,6 +4050,7 @@ func TestServerHandlerConnectionClose(t *testing.T) {
})
var sawGoAway bool
var sawRes bool
+ var sawWindowUpdate bool
for {
f, err := st.readFrame()
if err == io.EOF {
@@ -3983,10 +4062,29 @@ func TestServerHandlerConnectionClose(t *testing.T) {
switch f := f.(type) {
case *GoAwayFrame:
sawGoAway = true
- unblockHandler <- true
if f.LastStreamID != 1 || f.ErrCode != ErrCodeNo {
t.Errorf("unexpected GOAWAY frame: %v", summarizeFrame(f))
}
+ // Create a stream and reset it.
+ // The server should ignore the stream.
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 3,
+ BlockFragment: st.encodeHeader(),
+ EndStream: false,
+ EndHeaders: true,
+ })
+ st.fr.WriteRSTStream(3, ErrCodeCancel)
+ // Create a stream and send data to it.
+ // The server should return flow control, even though it
+ // does not process the stream.
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 5,
+ BlockFragment: st.encodeHeader(),
+ EndStream: false,
+ EndHeaders: true,
+ })
+ // Write enough data to trigger a window update.
+ st.writeData(5, true, make([]byte, 1<<19))
case *HeadersFrame:
goth := st.decodeHeader(f.HeaderBlockFragment())
wanth := [][2]string{
@@ -4001,6 +4099,17 @@ func TestServerHandlerConnectionClose(t *testing.T) {
if f.StreamID != 1 || !f.StreamEnded() || len(f.Data()) != 0 {
t.Errorf("unexpected DATA frame: %v", summarizeFrame(f))
}
+ case *WindowUpdateFrame:
+ if !sawGoAway {
+ t.Errorf("unexpected WINDOW_UPDATE frame: %v", summarizeFrame(f))
+ return
+ }
+ if f.StreamID != 0 {
+ st.t.Fatalf("WindowUpdate StreamID = %d; want 5", f.FrameHeader.StreamID)
+ return
+ }
+ sawWindowUpdate = true
+ unblockHandler <- true
default:
t.Logf("unexpected frame: %v", summarizeFrame(f))
}
@@ -4011,6 +4120,9 @@ func TestServerHandlerConnectionClose(t *testing.T) {
if !sawRes {
t.Errorf("didn't see response")
}
+ if !sawWindowUpdate {
+ t.Errorf("didn't see WINDOW_UPDATE")
+ }
})
}
@@ -4204,7 +4316,8 @@ func TestContentEncodingNoSniffing(t *testing.T) {
}
func TestServerWindowUpdateOnBodyClose(t *testing.T) {
- const content = "12345678"
+ const windowSize = 65535 * 2
+ content := make([]byte, windowSize)
blockCh := make(chan bool)
errc := make(chan error, 1)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
@@ -4221,6 +4334,9 @@ func TestServerWindowUpdateOnBodyClose(t *testing.T) {
blockCh <- true
<-blockCh
errc <- nil
+ }, func(s *Server) {
+ s.MaxUploadBufferPerConnection = windowSize
+ s.MaxUploadBufferPerStream = windowSize
})
defer st.Close()
@@ -4234,12 +4350,13 @@ func TestServerWindowUpdateOnBodyClose(t *testing.T) {
EndStream: false, // to say DATA frames are coming
EndHeaders: true,
})
- st.writeData(1, false, []byte(content[:5]))
+ st.writeData(1, false, content[:windowSize/2])
<-blockCh
st.stream(1).body.CloseWithError(io.EOF)
- st.writeData(1, false, []byte(content[5:]))
blockCh <- true
+ // Wait for flow control credit for the portion of the request written so far.
+ increments := windowSize / 2
for {
f, err := st.readFrame()
if err == io.EOF {
@@ -4248,16 +4365,18 @@ func TestServerWindowUpdateOnBodyClose(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if rs, ok := f.(*RSTStreamFrame); ok && rs.StreamID == 1 {
- break
- }
if wu, ok := f.(*WindowUpdateFrame); ok && wu.StreamID == 0 {
- if e, a := uint32(3), wu.Increment; e != a {
- t.Errorf("Increment=%d, want %d", a, e)
+ increments -= int(wu.Increment)
+ if increments == 0 {
+ break
}
}
}
+ // Writing data after the stream is reset immediately returns flow control credit.
+ st.writeData(1, false, content[windowSize/2:])
+ st.wantWindowUpdate(0, windowSize/2)
+
if err := <-errc; err != nil {
t.Error(err)
}
@@ -4396,27 +4515,23 @@ func TestServerSendsEarlyHints(t *testing.T) {
func TestProtocolErrorAfterGoAway(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(200)
- w.(http.Flusher).Flush()
io.Copy(io.Discard, r.Body)
})
defer st.Close()
st.greet()
+ content := "some content"
st.writeHeaders(HeadersFrameParam{
StreamID: 1,
BlockFragment: st.encodeHeader(
":method", "POST",
- "content-length", "1",
+ "content-length", strconv.Itoa(len(content)),
),
EndStream: false,
EndHeaders: true,
})
-
- _, err := st.readFrame()
- if err != nil {
- st.t.Fatal(err)
- }
+ st.writeData(1, false, []byte(content[:5]))
+ st.writeReadPing()
// Send a GOAWAY with ErrCodeNo, followed by a bogus window update.
// The server should close the connection.
@@ -4490,3 +4605,104 @@ func TestServerInitialFlowControlWindow(t *testing.T) {
})
}
}
+
+// TestCanonicalHeaderCacheGrowth verifies that the canonical header cache
+// size is capped to a reasonable level.
+func TestCanonicalHeaderCacheGrowth(t *testing.T) {
+ for _, size := range []int{1, (1 << 20) - 10} {
+ base := strings.Repeat("X", size)
+ sc := &serverConn{
+ serveG: newGoroutineLock(),
+ }
+ const count = 1000
+ for i := 0; i < count; i++ {
+ h := fmt.Sprintf("%v-%v", base, i)
+ c := sc.canonicalHeader(h)
+ if len(h) != len(c) {
+ t.Errorf("sc.canonicalHeader(%q) = %q, want same length", h, c)
+ }
+ }
+ total := 0
+ for k, v := range sc.canonHeader {
+ total += len(k) + len(v) + 100
+ }
+ if total > maxCachedCanonicalHeadersKeysSize {
+ t.Errorf("after adding %v ~%v-byte headers, canonHeader cache is ~%v bytes, want <%v", count, size, total, maxCachedCanonicalHeadersKeysSize)
+ }
+ }
+}
+
+// TestServerWriteDoesNotRetainBufferAfterStreamClose checks for access to
+// the slice passed to ResponseWriter.Write after Write returns.
+//
+// Terminating the request stream on the client causes Write to return.
+// We should not access the slice after this point.
+func TestServerWriteDoesNotRetainBufferAfterReturn(t *testing.T) {
+ donec := make(chan struct{})
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ defer close(donec)
+ buf := make([]byte, 1<<20)
+ var i byte
+ for {
+ i++
+ _, err := w.Write(buf)
+ for j := range buf {
+ buf[j] = byte(i) // trigger race detector
+ }
+ if err != nil {
+ return
+ }
+ }
+ }, optOnlyServer)
+ defer st.Close()
+
+ tr := &Transport{TLSClientConfig: tlsConfigInsecure}
+ defer tr.CloseIdleConnections()
+
+ req, _ := http.NewRequest("GET", st.ts.URL, nil)
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res.Body.Close()
+ <-donec
+}
+
+// TestServerWriteDoesNotRetainBufferAfterServerClose checks for access to
+// the slice passed to ResponseWriter.Write after Write returns.
+//
+// Shutting down the Server causes Write to return.
+// We should not access the slice after this point.
+func TestServerWriteDoesNotRetainBufferAfterServerClose(t *testing.T) {
+ donec := make(chan struct{}, 1)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ donec <- struct{}{}
+ defer close(donec)
+ buf := make([]byte, 1<<20)
+ var i byte
+ for {
+ i++
+ _, err := w.Write(buf)
+ for j := range buf {
+ buf[j] = byte(i)
+ }
+ if err != nil {
+ return
+ }
+ }
+ }, optOnlyServer)
+ defer st.Close()
+
+ tr := &Transport{TLSClientConfig: tlsConfigInsecure}
+ defer tr.CloseIdleConnections()
+
+ req, _ := http.NewRequest("GET", st.ts.URL, nil)
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ <-donec
+ st.ts.Config.Close()
+ <-donec
+}
diff --git a/http2/transport.go b/http2/transport.go
index c5d005bba7..05ba23d3d9 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"io"
+ "io/fs"
"log"
"math"
mathrand "math/rand"
@@ -46,10 +47,6 @@ const (
// we buffer per stream.
transportDefaultStreamFlow = 4 << 20
- // transportDefaultStreamMinRefresh is the minimum number of bytes we'll send
- // a stream-level WINDOW_UPDATE for at a time.
- transportDefaultStreamMinRefresh = 4 << 10
-
defaultUserAgent = "Go-http-client/2.0"
// initialMaxConcurrentStreams is a connections maxConcurrentStreams until
@@ -117,6 +114,28 @@ type Transport struct {
// to mean no limit.
MaxHeaderListSize uint32
+ // MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the
+ // initial settings frame. It is the size in bytes of the largest frame
+ // payload that the sender is willing to receive. If 0, no setting is
+ // sent, and the value is provided by the peer, which should be 16384
+ // according to the spec:
+ // https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2.
+ // Values are bounded in the range 16k to 16M.
+ MaxReadFrameSize uint32
+
+ // MaxDecoderHeaderTableSize optionally specifies the http2
+ // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+ // informs the remote endpoint of the maximum size of the header compression
+ // table used to decode header blocks, in octets. If zero, the default value
+ // of 4096 is used.
+ MaxDecoderHeaderTableSize uint32
+
+ // MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+ // header compression table used for encoding request headers. Received
+ // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+ // the default value of 4096 is used.
+ MaxEncoderHeaderTableSize uint32
+
// StrictMaxConcurrentStreams controls whether the server's
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
// globally. If false, new TCP connections are created to the
@@ -170,6 +189,19 @@ func (t *Transport) maxHeaderListSize() uint32 {
return t.MaxHeaderListSize
}
+func (t *Transport) maxFrameReadSize() uint32 {
+ if t.MaxReadFrameSize == 0 {
+ return 0 // use the default provided by the peer
+ }
+ if t.MaxReadFrameSize < minMaxFrameSize {
+ return minMaxFrameSize
+ }
+ if t.MaxReadFrameSize > maxFrameSize {
+ return maxFrameSize
+ }
+ return t.MaxReadFrameSize
+}
+
func (t *Transport) disableCompression() bool {
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
}
@@ -274,8 +306,8 @@ type ClientConn struct {
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
- flow flow // our conn-level flow control quota (cs.flow is per stream)
- inflow flow // peer's conn-level flow control
+ flow outflow // our conn-level flow control quota (cs.outflow is per stream)
+ inflow inflow // peer's conn-level flow control
doNotReuse bool // whether conn is marked to not be reused for any future requests
closing bool
closed bool
@@ -292,10 +324,11 @@ type ClientConn struct {
lastActive time.Time
lastIdle time.Time // time last idle
// Settings from peer: (also guarded by wmu)
- maxFrameSize uint32
- maxConcurrentStreams uint32
- peerMaxHeaderListSize uint64
- initialWindowSize uint32
+ maxFrameSize uint32
+ maxConcurrentStreams uint32
+ peerMaxHeaderListSize uint64
+ peerMaxHeaderTableSize uint32
+ initialWindowSize uint32
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -339,10 +372,10 @@ type clientStream struct {
respHeaderRecv chan struct{} // closed when headers are received
res *http.Response // set if respHeaderRecv is closed
- flow flow // guarded by cc.mu
- inflow flow // guarded by cc.mu
- bytesRemain int64 // -1 means unknown; owned by transportResponseBody.Read
- readErr error // sticky read error; owned by transportResponseBody.Read
+ flow outflow // guarded by cc.mu
+ inflow inflow // guarded by cc.mu
+ bytesRemain int64 // -1 means unknown; owned by transportResponseBody.Read
+ readErr error // sticky read error; owned by transportResponseBody.Read
reqBody io.ReadCloser
reqBodyContentLength int64 // -1 means unknown
@@ -501,6 +534,15 @@ func authorityAddr(scheme string, authority string) (addr string) {
return net.JoinHostPort(host, port)
}
+var retryBackoffHook func(time.Duration) *time.Timer
+
+func backoffNewTimer(d time.Duration) *time.Timer {
+ if retryBackoffHook != nil {
+ return retryBackoffHook(d)
+ }
+ return time.NewTimer(d)
+}
+
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@@ -526,11 +568,14 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
}
backoff := float64(uint(1) << (uint(retry) - 1))
backoff += backoff * (0.1 * mathrand.Float64())
+ d := time.Second * time.Duration(backoff)
+ timer := backoffNewTimer(d)
select {
- case <-time.After(time.Second * time.Duration(backoff)):
+ case <-timer.C:
t.vlogf("RoundTrip retrying after failure: %v", err)
continue
case <-req.Context().Done():
+ timer.Stop()
err = req.Context().Err()
}
}
@@ -668,6 +713,20 @@ func (t *Transport) expectContinueTimeout() time.Duration {
return t.t1.ExpectContinueTimeout
}
+func (t *Transport) maxDecoderHeaderTableSize() uint32 {
+ if v := t.MaxDecoderHeaderTableSize; v > 0 {
+ return v
+ }
+ return initialHeaderTableSize
+}
+
+func (t *Transport) maxEncoderHeaderTableSize() uint32 {
+ if v := t.MaxEncoderHeaderTableSize; v > 0 {
+ return v
+ }
+ return initialHeaderTableSize
+}
+
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
return t.newClientConn(c, t.disableKeepAlives())
}
@@ -708,15 +767,19 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
})
cc.br = bufio.NewReader(c)
cc.fr = NewFramer(cc.bw, cc.br)
+ if t.maxFrameReadSize() != 0 {
+ cc.fr.SetMaxReadFrameSize(t.maxFrameReadSize())
+ }
if t.CountError != nil {
cc.fr.countError = t.CountError
}
- cc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+ maxHeaderTableSize := t.maxDecoderHeaderTableSize()
+ cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil)
cc.fr.MaxHeaderListSize = t.maxHeaderListSize()
- // TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on
- // henc in response to SETTINGS frames?
cc.henc = hpack.NewEncoder(&cc.hbuf)
+ cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
+ cc.peerMaxHeaderTableSize = initialHeaderTableSize
if t.AllowHTTP {
cc.nextStreamID = 3
@@ -731,14 +794,20 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
{ID: SettingEnablePush, Val: 0},
{ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow},
}
+ if max := t.maxFrameReadSize(); max != 0 {
+ initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max})
+ }
if max := t.maxHeaderListSize(); max != 0 {
initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max})
}
+ if maxHeaderTableSize != initialHeaderTableSize {
+ initialSettings = append(initialSettings, Setting{ID: SettingHeaderTableSize, Val: maxHeaderTableSize})
+ }
cc.bw.Write(clientPreface)
cc.fr.WriteSettings(initialSettings...)
cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow)
- cc.inflow.add(transportDefaultConnFlow + initialWindowSize)
+ cc.inflow.init(transportDefaultConnFlow + initialWindowSize)
cc.bw.Flush()
if cc.werr != nil {
cc.Close()
@@ -1075,7 +1144,7 @@ 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 = http.CanonicalHeaderKey(k)
+ k = canonicalHeader(k)
switch k {
case "Transfer-Encoding", "Trailer", "Content-Length":
return "", fmt.Errorf("invalid Trailer key %q", k)
@@ -1500,7 +1569,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
close(cs.donec)
}
-// awaitOpenSlotForStream waits until len(streams) < maxConcurrentStreams.
+// awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
// Must hold cc.mu.
func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
for {
@@ -1612,7 +1681,7 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
var sawEOF bool
for !sawEOF {
- n, err := body.Read(buf[:len(buf)])
+ n, err := body.Read(buf)
if hasContentLen {
remainLen -= int64(n)
if remainLen == 0 && err == nil {
@@ -1915,7 +1984,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
// Header list size is ok. Write the headers.
enumerateHeaders(func(name, value string) {
- name, ascii := asciiToLower(name)
+ 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).
@@ -1968,7 +2037,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
}
for k, vv := range trailer {
- lowKey, ascii := asciiToLower(k)
+ lowKey, ascii := 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).
@@ -2000,8 +2069,7 @@ type resAndError struct {
func (cc *ClientConn) addStreamLocked(cs *clientStream) {
cs.flow.add(int32(cc.initialWindowSize))
cs.flow.setConnFlow(&cc.flow)
- cs.inflow.add(transportDefaultStreamFlow)
- cs.inflow.setConnFlow(&cc.inflow)
+ cs.inflow.init(transportDefaultStreamFlow)
cs.ID = cc.nextStreamID
cc.nextStreamID += 2
cc.streams[cs.ID] = cs
@@ -2301,7 +2369,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
Status: status + " " + http.StatusText(statusCode),
}
for _, hf := range regularFields {
- key := http.CanonicalHeaderKey(hf.Name)
+ key := canonicalHeader(hf.Name)
if key == "Trailer" {
t := res.Trailer
if t == nil {
@@ -2309,7 +2377,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
res.Trailer = t
}
foreachHeaderElement(hf.Value, func(v string) {
- t[http.CanonicalHeaderKey(v)] = nil
+ t[canonicalHeader(v)] = nil
})
} else {
vv := header[key]
@@ -2414,7 +2482,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr
trailer := make(http.Header)
for _, hf := range f.RegularFields() {
- key := http.CanonicalHeaderKey(hf.Name)
+ key := canonicalHeader(hf.Name)
trailer[key] = append(trailer[key], hf.Value)
}
cs.trailer = trailer
@@ -2460,21 +2528,10 @@ func (b transportResponseBody) Read(p []byte) (n int, err error) {
}
cc.mu.Lock()
- var connAdd, streamAdd int32
- // Check the conn-level first, before the stream-level.
- if v := cc.inflow.available(); v < transportDefaultConnFlow/2 {
- connAdd = transportDefaultConnFlow - v
- cc.inflow.add(connAdd)
- }
+ connAdd := cc.inflow.add(n)
+ var streamAdd int32
if err == nil { // No need to refresh if the stream is over or failed.
- // Consider any buffered body data (read from the conn but not
- // consumed by the client) when computing flow control for this
- // stream.
- v := int(cs.inflow.available()) + cs.bufPipe.Len()
- if v < transportDefaultStreamFlow-transportDefaultStreamMinRefresh {
- streamAdd = int32(transportDefaultStreamFlow - v)
- cs.inflow.add(streamAdd)
- }
+ streamAdd = cs.inflow.add(n)
}
cc.mu.Unlock()
@@ -2502,17 +2559,15 @@ func (b transportResponseBody) Close() error {
if unread > 0 {
cc.mu.Lock()
// Return connection-level flow control.
- if unread > 0 {
- cc.inflow.add(int32(unread))
- }
+ connAdd := cc.inflow.add(unread)
cc.mu.Unlock()
// TODO(dneil): Acquiring this mutex can block indefinitely.
// Move flow control return to a goroutine?
cc.wmu.Lock()
// Return connection-level flow control.
- if unread > 0 {
- cc.fr.WriteWindowUpdate(0, uint32(unread))
+ if connAdd > 0 {
+ cc.fr.WriteWindowUpdate(0, uint32(connAdd))
}
cc.bw.Flush()
cc.wmu.Unlock()
@@ -2555,13 +2610,18 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
// But at least return their flow control:
if f.Length > 0 {
cc.mu.Lock()
- cc.inflow.add(int32(f.Length))
+ ok := cc.inflow.take(f.Length)
+ connAdd := cc.inflow.add(int(f.Length))
cc.mu.Unlock()
-
- cc.wmu.Lock()
- cc.fr.WriteWindowUpdate(0, uint32(f.Length))
- cc.bw.Flush()
- cc.wmu.Unlock()
+ if !ok {
+ return ConnectionError(ErrCodeFlowControl)
+ }
+ if connAdd > 0 {
+ cc.wmu.Lock()
+ cc.fr.WriteWindowUpdate(0, uint32(connAdd))
+ cc.bw.Flush()
+ cc.wmu.Unlock()
+ }
}
return nil
}
@@ -2592,9 +2652,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
}
// Check connection-level flow control.
cc.mu.Lock()
- if cs.inflow.available() >= int32(f.Length) {
- cs.inflow.take(int32(f.Length))
- } else {
+ if !takeInflows(&cc.inflow, &cs.inflow, f.Length) {
cc.mu.Unlock()
return ConnectionError(ErrCodeFlowControl)
}
@@ -2616,19 +2674,20 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
}
}
- if refund > 0 {
- cc.inflow.add(int32(refund))
- if !didReset {
- cs.inflow.add(int32(refund))
- }
+ sendConn := cc.inflow.add(refund)
+ var sendStream int32
+ if !didReset {
+ sendStream = cs.inflow.add(refund)
}
cc.mu.Unlock()
- if refund > 0 {
+ if sendConn > 0 || sendStream > 0 {
cc.wmu.Lock()
- cc.fr.WriteWindowUpdate(0, uint32(refund))
- if !didReset {
- cc.fr.WriteWindowUpdate(cs.ID, uint32(refund))
+ if sendConn > 0 {
+ cc.fr.WriteWindowUpdate(0, uint32(sendConn))
+ }
+ if sendStream > 0 {
+ cc.fr.WriteWindowUpdate(cs.ID, uint32(sendStream))
}
cc.bw.Flush()
cc.wmu.Unlock()
@@ -2760,8 +2819,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
cc.cond.Broadcast()
cc.initialWindowSize = s.Val
+ case SettingHeaderTableSize:
+ cc.henc.SetMaxDynamicTableSize(s.Val)
+ cc.peerMaxHeaderTableSize = s.Val
default:
- // TODO(bradfitz): handle more settings? SETTINGS_HEADER_TABLE_SIZE probably.
cc.vlogf("Unhandled Setting: %v", s)
}
return nil
@@ -2985,7 +3046,11 @@ func (gz *gzipReader) Read(p []byte) (n int, err error) {
}
func (gz *gzipReader) Close() error {
- return gz.body.Close()
+ if err := gz.body.Close(); err != nil {
+ return err
+ }
+ gz.zerr = fs.ErrClosed
+ return nil
}
type errorReader struct{ err error }
diff --git a/http2/transport_test.go b/http2/transport_test.go
index 67afb57c36..5adef42922 100644
--- a/http2/transport_test.go
+++ b/http2/transport_test.go
@@ -7,6 +7,7 @@ package http2
import (
"bufio"
"bytes"
+ "compress/gzip"
"context"
"crypto/tls"
"encoding/hex"
@@ -14,6 +15,7 @@ import (
"flag"
"fmt"
"io"
+ "io/fs"
"io/ioutil"
"log"
"math/rand"
@@ -739,12 +741,13 @@ func (fw flushWriter) Write(p []byte) (n int, err error) {
}
type clientTester struct {
- t *testing.T
- tr *Transport
- sc, cc net.Conn // server and client conn
- fr *Framer // server's framer
- client func() error
- server func() error
+ t *testing.T
+ tr *Transport
+ sc, cc net.Conn // server and client conn
+ fr *Framer // server's framer
+ settings *SettingsFrame
+ client func() error
+ server func() error
}
func newClientTester(t *testing.T) *clientTester {
@@ -807,9 +810,9 @@ func (ct *clientTester) greet(settings ...Setting) {
if err != nil {
ct.t.Fatalf("Reading client settings frame: %v", err)
}
- if sf, ok := f.(*SettingsFrame); !ok {
+ var ok bool
+ if ct.settings, ok = f.(*SettingsFrame); !ok {
ct.t.Fatalf("Wanted client settings frame; got %v", f)
- _ = sf // stash it away?
}
if err := ct.fr.WriteSettings(settings...); err != nil {
ct.t.Fatal(err)
@@ -832,6 +835,55 @@ func (ct *clientTester) readNonSettingsFrame() (Frame, error) {
}
}
+// writeReadPing sends a PING and immediately reads the PING ACK.
+// It will fail if any other unread data was pending on the connection,
+// aside from SETTINGS frames.
+func (ct *clientTester) writeReadPing() error {
+ data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
+ if err := ct.fr.WritePing(false, data); err != nil {
+ return fmt.Errorf("Error writing PING: %v", err)
+ }
+ f, err := ct.readNonSettingsFrame()
+ if err != nil {
+ return err
+ }
+ p, ok := f.(*PingFrame)
+ if !ok {
+ return fmt.Errorf("got a %v, want a PING ACK", f)
+ }
+ if p.Flags&FlagPingAck == 0 {
+ return fmt.Errorf("got a PING, want a PING ACK")
+ }
+ if p.Data != data {
+ return fmt.Errorf("got PING data = %x, want %x", p.Data, data)
+ }
+ return nil
+}
+
+func (ct *clientTester) inflowWindow(streamID uint32) int32 {
+ pool := ct.tr.connPoolOrDef.(*clientConnPool)
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+ if n := len(pool.keys); n != 1 {
+ ct.t.Errorf("clientConnPool contains %v keys, expected 1", n)
+ return -1
+ }
+ for cc := range pool.keys {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ if streamID == 0 {
+ return cc.inflow.avail + cc.inflow.unsent
+ }
+ cs := cc.streams[streamID]
+ if cs == nil {
+ ct.t.Errorf("no stream with id %v", streamID)
+ return -1
+ }
+ return cs.inflow.avail + cs.inflow.unsent
+ }
+ return -1
+}
+
func (ct *clientTester) cleanup() {
ct.tr.CloseIdleConnections()
@@ -2503,6 +2555,28 @@ func TestGzipReader_DoubleReadCrash(t *testing.T) {
}
}
+func TestGzipReader_ReadAfterClose(t *testing.T) {
+ body := bytes.Buffer{}
+ w := gzip.NewWriter(&body)
+ w.Write([]byte("012345679"))
+ w.Close()
+ gz := &gzipReader{
+ body: ioutil.NopCloser(&body),
+ }
+ var buf [1]byte
+ n, err := gz.Read(buf[:])
+ if n != 1 || err != nil {
+ t.Fatalf("first Read = %v, %v; want 1, nil", n, err)
+ }
+ if err := gz.Close(); err != nil {
+ t.Fatalf("gz Close error: %v", err)
+ }
+ n, err = gz.Read(buf[:])
+ if n != 0 || err != fs.ErrClosed {
+ t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err)
+ }
+}
+
func TestTransportNewTLSConfig(t *testing.T) {
tests := [...]struct {
conf *tls.Config
@@ -2880,22 +2954,17 @@ func testTransportUsesGoAwayDebugError(t *testing.T, failMidBody bool) {
func testTransportReturnsUnusedFlowControl(t *testing.T, oneDataFrame bool) {
ct := newClientTester(t)
- clientClosed := make(chan struct{})
- serverWroteFirstByte := make(chan struct{})
-
ct.client = func() error {
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
res, err := ct.tr.RoundTrip(req)
if err != nil {
return err
}
- <-serverWroteFirstByte
if n, err := res.Body.Read(make([]byte, 1)); err != nil || n != 1 {
return fmt.Errorf("body read = %v, %v; want 1, nil", n, err)
}
res.Body.Close() // leaving 4999 bytes unread
- close(clientClosed)
return nil
}
@@ -2930,6 +2999,7 @@ func testTransportReturnsUnusedFlowControl(t *testing.T, oneDataFrame bool) {
EndStream: false,
BlockFragment: buf.Bytes(),
})
+ initialInflow := ct.inflowWindow(0)
// Two cases:
// - Send one DATA frame with 5000 bytes.
@@ -2938,50 +3008,63 @@ func testTransportReturnsUnusedFlowControl(t *testing.T, oneDataFrame bool) {
// In both cases, the client should consume one byte of data,
// refund that byte, then refund the following 4999 bytes.
//
- // In the second case, the server waits for the client connection to
- // close before seconding the second DATA frame. This tests the case
+ // In the second case, the server waits for the client to reset the
+ // stream before sending the second DATA frame. This tests the case
// where the client receives a DATA frame after it has reset the stream.
if oneDataFrame {
ct.fr.WriteData(hf.StreamID, false /* don't end stream */, make([]byte, 5000))
- close(serverWroteFirstByte)
- <-clientClosed
} else {
ct.fr.WriteData(hf.StreamID, false /* don't end stream */, make([]byte, 1))
- close(serverWroteFirstByte)
- <-clientClosed
- ct.fr.WriteData(hf.StreamID, false /* don't end stream */, make([]byte, 4999))
}
- waitingFor := "RSTStreamFrame"
- sawRST := false
- sawWUF := false
- for !sawRST && !sawWUF {
- f, err := ct.fr.ReadFrame()
+ wantRST := true
+ wantWUF := true
+ if !oneDataFrame {
+ wantWUF = false // flow control update is small, and will not be sent
+ }
+ for wantRST || wantWUF {
+ f, err := ct.readNonSettingsFrame()
if err != nil {
- return fmt.Errorf("ReadFrame while waiting for %s: %v", waitingFor, err)
+ return err
}
switch f := f.(type) {
- case *SettingsFrame:
case *RSTStreamFrame:
- if sawRST {
- return fmt.Errorf("saw second RSTStreamFrame: %v", summarizeFrame(f))
+ if !wantRST {
+ return fmt.Errorf("Unexpected frame: %v", summarizeFrame(f))
}
if f.ErrCode != ErrCodeCancel {
return fmt.Errorf("Expected a RSTStreamFrame with code cancel; got %v", summarizeFrame(f))
}
- sawRST = true
+ wantRST = false
case *WindowUpdateFrame:
- if sawWUF {
- return fmt.Errorf("saw second WindowUpdateFrame: %v", summarizeFrame(f))
+ if !wantWUF {
+ return fmt.Errorf("Unexpected frame: %v", summarizeFrame(f))
}
- if f.Increment != 4999 {
+ if f.Increment != 5000 {
return fmt.Errorf("Expected WindowUpdateFrames for 5000 bytes; got %v", summarizeFrame(f))
}
- sawWUF = true
+ wantWUF = false
default:
return fmt.Errorf("Unexpected frame: %v", summarizeFrame(f))
}
}
+ if !oneDataFrame {
+ ct.fr.WriteData(hf.StreamID, false /* don't end stream */, make([]byte, 4999))
+ f, err := ct.readNonSettingsFrame()
+ if err != nil {
+ return err
+ }
+ wuf, ok := f.(*WindowUpdateFrame)
+ if !ok || wuf.Increment != 5000 {
+ return fmt.Errorf("want WindowUpdateFrame for 5000 bytes; got %v", summarizeFrame(f))
+ }
+ }
+ if err := ct.writeReadPing(); err != nil {
+ return err
+ }
+ if got, want := ct.inflowWindow(0), initialInflow; got != want {
+ return fmt.Errorf("connection flow tokens = %v, want %v", got, want)
+ }
return nil
}
ct.run()
@@ -3108,6 +3191,8 @@ func TestTransportReturnsDataPaddingFlowControl(t *testing.T) {
break
}
+ initialConnWindow := ct.inflowWindow(0)
+
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
@@ -3118,24 +3203,18 @@ func TestTransportReturnsDataPaddingFlowControl(t *testing.T) {
EndStream: false,
BlockFragment: buf.Bytes(),
})
+ initialStreamWindow := ct.inflowWindow(hf.StreamID)
pad := make([]byte, 5)
ct.fr.WriteDataPadded(hf.StreamID, false, make([]byte, 5000), pad) // without ending stream
-
- f, err := ct.readNonSettingsFrame()
- if err != nil {
- return fmt.Errorf("ReadFrame while waiting for first WindowUpdateFrame: %v", err)
- }
- wantBack := uint32(len(pad)) + 1 // one byte for the length of the padding
- if wuf, ok := f.(*WindowUpdateFrame); !ok || wuf.Increment != wantBack || wuf.StreamID != 0 {
- return fmt.Errorf("Expected conn WindowUpdateFrame for %d bytes; got %v", wantBack, summarizeFrame(f))
+ if err := ct.writeReadPing(); err != nil {
+ return err
}
-
- f, err = ct.readNonSettingsFrame()
- if err != nil {
- return fmt.Errorf("ReadFrame while waiting for second WindowUpdateFrame: %v", err)
+ // Padding flow control should have been returned.
+ if got, want := ct.inflowWindow(0), initialConnWindow-5000; got != want {
+ t.Errorf("conn inflow window = %v, want %v", got, want)
}
- if wuf, ok := f.(*WindowUpdateFrame); !ok || wuf.Increment != wantBack || wuf.StreamID == 0 {
- return fmt.Errorf("Expected stream WindowUpdateFrame for %d bytes; got %v", wantBack, summarizeFrame(f))
+ if got, want := ct.inflowWindow(hf.StreamID), initialStreamWindow-5000; got != want {
+ t.Errorf("stream inflow window = %v, want %v", got, want)
}
unblockClient <- true
return nil
@@ -3857,6 +3936,12 @@ func TestTransportRetryHasLimit(t *testing.T) {
if testing.Short() {
t.Skip("skipping long test in short mode")
}
+ retryBackoffHook = func(d time.Duration) *time.Timer {
+ return time.NewTimer(0) // fires immediately
+ }
+ defer func() {
+ retryBackoffHook = nil
+ }()
clientDone := make(chan struct{})
ct := newClientTester(t)
ct.client = func() error {
@@ -3968,6 +4053,44 @@ func TestTransportResponseDataBeforeHeaders(t *testing.T) {
ct.run()
}
+func TestTransportMaxFrameReadSize(t *testing.T) {
+ for _, test := range []struct {
+ maxReadFrameSize uint32
+ want uint32
+ }{{
+ maxReadFrameSize: 64000,
+ want: 64000,
+ }, {
+ maxReadFrameSize: 1024,
+ want: minMaxFrameSize,
+ }} {
+ ct := newClientTester(t)
+ ct.tr.MaxReadFrameSize = test.maxReadFrameSize
+ ct.client = func() error {
+ req, _ := http.NewRequest("GET", "https://dummy.tld/", http.NoBody)
+ ct.tr.RoundTrip(req)
+ return nil
+ }
+ ct.server = func() error {
+ defer ct.cc.(*net.TCPConn).Close()
+ ct.greet()
+ var got uint32
+ ct.settings.ForeachSetting(func(s Setting) error {
+ switch s.ID {
+ case SettingMaxFrameSize:
+ got = s.Val
+ }
+ return nil
+ })
+ if got != test.want {
+ t.Errorf("Transport.MaxReadFrameSize = %v; server got %v, want %v", test.maxReadFrameSize, got, test.want)
+ }
+ return nil
+ }
+ ct.run()
+ }
+}
+
func TestTransportRequestsLowServerLimit(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
}, optOnlyServer, func(s *Server) {
@@ -4193,6 +4316,150 @@ func TestTransportRequestsStallAtServerLimit(t *testing.T) {
ct.run()
}
+func TestTransportMaxDecoderHeaderTableSize(t *testing.T) {
+ ct := newClientTester(t)
+ var reqSize, resSize uint32 = 8192, 16384
+ ct.tr.MaxDecoderHeaderTableSize = reqSize
+ ct.client = func() error {
+ req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
+ cc, err := ct.tr.NewClientConn(ct.cc)
+ if err != nil {
+ return err
+ }
+ _, err = cc.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ if got, want := cc.peerMaxHeaderTableSize, resSize; got != want {
+ return fmt.Errorf("peerHeaderTableSize = %d, want %d", got, want)
+ }
+ return nil
+ }
+ ct.server = func() error {
+ buf := make([]byte, len(ClientPreface))
+ _, err := io.ReadFull(ct.sc, buf)
+ if err != nil {
+ return fmt.Errorf("reading client preface: %v", err)
+ }
+ f, err := ct.fr.ReadFrame()
+ if err != nil {
+ return err
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ ct.t.Fatalf("wanted client settings frame; got %v", f)
+ _ = sf // stash it away?
+ }
+ var found bool
+ err = sf.ForeachSetting(func(s Setting) error {
+ if s.ID == SettingHeaderTableSize {
+ found = true
+ if got, want := s.Val, reqSize; got != want {
+ return fmt.Errorf("received SETTINGS_HEADER_TABLE_SIZE = %d, want %d", got, want)
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ if !found {
+ return fmt.Errorf("missing SETTINGS_HEADER_TABLE_SIZE setting")
+ }
+ if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, resSize}); err != nil {
+ ct.t.Fatal(err)
+ }
+ if err := ct.fr.WriteSettingsAck(); err != nil {
+ ct.t.Fatal(err)
+ }
+
+ for {
+ f, err := ct.fr.ReadFrame()
+ if err != nil {
+ return err
+ }
+ switch f := f.(type) {
+ case *HeadersFrame:
+ var buf bytes.Buffer
+ enc := hpack.NewEncoder(&buf)
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
+ ct.fr.WriteHeaders(HeadersFrameParam{
+ StreamID: f.StreamID,
+ EndHeaders: true,
+ EndStream: true,
+ BlockFragment: buf.Bytes(),
+ })
+ return nil
+ }
+ }
+ }
+ ct.run()
+}
+
+func TestTransportMaxEncoderHeaderTableSize(t *testing.T) {
+ ct := newClientTester(t)
+ var peerAdvertisedMaxHeaderTableSize uint32 = 16384
+ ct.tr.MaxEncoderHeaderTableSize = 8192
+ ct.client = func() error {
+ req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
+ cc, err := ct.tr.NewClientConn(ct.cc)
+ if err != nil {
+ return err
+ }
+ _, err = cc.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ if got, want := cc.henc.MaxDynamicTableSize(), ct.tr.MaxEncoderHeaderTableSize; got != want {
+ return fmt.Errorf("henc.MaxDynamicTableSize() = %d, want %d", got, want)
+ }
+ return nil
+ }
+ ct.server = func() error {
+ buf := make([]byte, len(ClientPreface))
+ _, err := io.ReadFull(ct.sc, buf)
+ if err != nil {
+ return fmt.Errorf("reading client preface: %v", err)
+ }
+ f, err := ct.fr.ReadFrame()
+ if err != nil {
+ return err
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ ct.t.Fatalf("wanted client settings frame; got %v", f)
+ _ = sf // stash it away?
+ }
+ if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, peerAdvertisedMaxHeaderTableSize}); err != nil {
+ ct.t.Fatal(err)
+ }
+ if err := ct.fr.WriteSettingsAck(); err != nil {
+ ct.t.Fatal(err)
+ }
+
+ for {
+ f, err := ct.fr.ReadFrame()
+ if err != nil {
+ return err
+ }
+ switch f := f.(type) {
+ case *HeadersFrame:
+ var buf bytes.Buffer
+ enc := hpack.NewEncoder(&buf)
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
+ ct.fr.WriteHeaders(HeadersFrameParam{
+ StreamID: f.StreamID,
+ EndHeaders: true,
+ EndStream: true,
+ BlockFragment: buf.Bytes(),
+ })
+ return nil
+ }
+ }
+ }
+ ct.run()
+}
+
func TestAuthorityAddr(t *testing.T) {
tests := []struct {
scheme, authority string
@@ -4434,6 +4701,61 @@ func BenchmarkClientResponseHeaders(b *testing.B) {
b.Run("1000 Headers", func(b *testing.B) { benchSimpleRoundTrip(b, 0, 1000) })
}
+func BenchmarkDownloadFrameSize(b *testing.B) {
+ b.Run(" 16k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 16*1024) })
+ b.Run(" 64k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 64*1024) })
+ b.Run("128k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 128*1024) })
+ b.Run("256k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 256*1024) })
+ b.Run("512k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 512*1024) })
+}
+func benchLargeDownloadRoundTrip(b *testing.B, frameSize uint32) {
+ defer disableGoroutineTracking()()
+ const transferSize = 1024 * 1024 * 1024 // must be multiple of 1M
+ b.ReportAllocs()
+ st := newServerTester(b,
+ func(w http.ResponseWriter, r *http.Request) {
+ // test 1GB transfer
+ w.Header().Set("Content-Length", strconv.Itoa(transferSize))
+ w.Header().Set("Content-Transfer-Encoding", "binary")
+ var data [1024 * 1024]byte
+ for i := 0; i < transferSize/(1024*1024); i++ {
+ w.Write(data[:])
+ }
+ }, optQuiet,
+ )
+ defer st.Close()
+
+ tr := &Transport{TLSClientConfig: tlsConfigInsecure, MaxReadFrameSize: frameSize}
+ defer tr.CloseIdleConnections()
+
+ req, err := http.NewRequest("GET", st.ts.URL, nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.N = 3
+ b.SetBytes(transferSize)
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ if res != nil {
+ res.Body.Close()
+ }
+ b.Fatalf("RoundTrip err = %v; want nil", err)
+ }
+ data, _ := io.ReadAll(res.Body)
+ if len(data) != transferSize {
+ b.Fatalf("Response length invalid")
+ }
+ res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ b.Fatalf("Response code = %v; want %v", res.StatusCode, http.StatusOK)
+ }
+ }
+}
+
func activeStreams(cc *ClientConn) int {
count := 0
cc.mu.Lock()
diff --git a/icmp/multipart.go b/icmp/multipart.go
index 5f36675594..c7b72bf3dd 100644
--- a/icmp/multipart.go
+++ b/icmp/multipart.go
@@ -33,7 +33,7 @@ func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts [
}
// multipartMessageOrigDatagramLen takes b as an original datagram,
-// and returns a required length for a padded orignal datagram in wire
+// and returns a required length for a padded original datagram in wire
// format.
func multipartMessageOrigDatagramLen(proto int, b []byte) int {
roundup := func(b []byte, align int) int {
diff --git a/internal/sockstest/server.go b/internal/sockstest/server.go
index dc2fa67c5e..c25a82f12a 100644
--- a/internal/sockstest/server.go
+++ b/internal/sockstest/server.go
@@ -46,7 +46,7 @@ func MarshalAuthReply(ver int, m socks.AuthMethod) ([]byte, error) {
return []byte{byte(ver), byte(m)}, nil
}
-// A CmdRequest repesents a command request.
+// A CmdRequest represents a command request.
type CmdRequest struct {
Version int
Cmd socks.Command
@@ -120,12 +120,12 @@ func MarshalCmdReply(ver int, reply socks.Reply, a *socks.Addr) ([]byte, error)
return b, nil
}
-// A Server repesents a server for handshake testing.
+// A Server represents a server for handshake testing.
type Server struct {
ln net.Listener
}
-// Addr rerurns a server address.
+// Addr returns a server address.
func (s *Server) Addr() net.Addr {
return s.ln.Addr()
}
diff --git a/ipv4/multicastlistener_test.go b/ipv4/multicastlistener_test.go
index 534ded6793..77bad6676c 100644
--- a/ipv4/multicastlistener_test.go
+++ b/ipv4/multicastlistener_test.go
@@ -142,7 +142,7 @@ func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) {
}
c, err := net.ListenPacket("udp4", net.JoinHostPort(ip.String(), port)) // unicast address with non-reusable port
if err != nil {
- // The listen may fail when the serivce is
+ // The listen may fail when the service is
// already in use, but it's fine because the
// purpose of this is not to test the
// bookkeeping of IP control block inside the
diff --git a/ipv6/bpf_test.go b/ipv6/bpf_test.go
index e249e1c923..c43ddd02ec 100644
--- a/ipv6/bpf_test.go
+++ b/ipv6/bpf_test.go
@@ -19,8 +19,8 @@ func TestBPF(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
l, err := net.ListenPacket("udp6", "[::1]:0")
diff --git a/ipv6/dgramopt.go b/ipv6/dgramopt.go
index 1f422e71dc..846f0e1f9c 100644
--- a/ipv6/dgramopt.go
+++ b/ipv6/dgramopt.go
@@ -245,7 +245,7 @@ func (c *dgramOpt) Checksum() (on bool, offset int, err error) {
return true, offset, nil
}
-// SetChecksum enables the kernel checksum processing. If on is ture,
+// SetChecksum enables the kernel checksum processing. If on is true,
// the offset should be an offset in bytes into the data of where the
// checksum field is located.
func (c *dgramOpt) SetChecksum(on bool, offset int) error {
diff --git a/ipv6/multicastlistener_test.go b/ipv6/multicastlistener_test.go
index 353327e017..a4dc86342e 100644
--- a/ipv6/multicastlistener_test.go
+++ b/ipv6/multicastlistener_test.go
@@ -142,7 +142,7 @@ func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) {
}
c, err := net.ListenPacket("udp6", net.JoinHostPort(ip.String()+"%"+ifi.Name, port)) // unicast address with non-reusable port
if err != nil {
- // The listen may fail when the serivce is
+ // The listen may fail when the service is
// already in use, but it's fine because the
// purpose of this is not to test the
// bookkeeping of IP control block inside the
diff --git a/ipv6/readwrite_test.go b/ipv6/readwrite_test.go
index e8db1199e1..131b1904c5 100644
--- a/ipv6/readwrite_test.go
+++ b/ipv6/readwrite_test.go
@@ -223,10 +223,10 @@ func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ ifi, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback)
+ if err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
-
c, err := nettest.NewLocalPacketListener("udp6")
if err != nil {
t.Fatal(err)
@@ -236,7 +236,6 @@ func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) {
defer p.Close()
dst := c.LocalAddr()
- ifi, _ := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback)
cf := ipv6.FlagTrafficClass | ipv6.FlagHopLimit | ipv6.FlagSrc | ipv6.FlagDst | ipv6.FlagInterface | ipv6.FlagPathMTU
wb := []byte("HELLO-R-U-THERE")
diff --git a/ipv6/sockopt_test.go b/ipv6/sockopt_test.go
index 3305cfc114..ab0d2e4e51 100644
--- a/ipv6/sockopt_test.go
+++ b/ipv6/sockopt_test.go
@@ -20,8 +20,9 @@ func TestConnInitiatorPathMTU(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
ln, err := net.Listen("tcp6", "[::1]:0")
@@ -53,8 +54,8 @@ func TestConnResponderPathMTU(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
ln, err := net.Listen("tcp6", "[::1]:0")
diff --git a/ipv6/unicast_test.go b/ipv6/unicast_test.go
index fe1d44dfa7..e03c2cd336 100644
--- a/ipv6/unicast_test.go
+++ b/ipv6/unicast_test.go
@@ -23,8 +23,8 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
c, err := nettest.NewLocalPacketListener("udp6")
diff --git a/ipv6/unicastsockopt_test.go b/ipv6/unicastsockopt_test.go
index ac0daf2856..c3abe2d14d 100644
--- a/ipv6/unicastsockopt_test.go
+++ b/ipv6/unicastsockopt_test.go
@@ -19,8 +19,8 @@ func TestConnUnicastSocketOptions(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
ln, err := net.Listen("tcp6", "[::1]:0")
@@ -64,8 +64,8 @@ func TestPacketConnUnicastSocketOptions(t *testing.T) {
case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if !nettest.SupportsIPv6() {
- t.Skip("ipv6 is not supported")
+ if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil {
+ t.Skip("ipv6 is not enabled for loopback interface")
}
ok := nettest.SupportsRawSocket()
diff --git a/nettest/nettest.go b/nettest/nettest.go
index 6918f2c362..510555ac28 100644
--- a/nettest/nettest.go
+++ b/nettest/nettest.go
@@ -20,11 +20,13 @@ import (
)
var (
- stackOnce sync.Once
- ipv4Enabled bool
- ipv6Enabled bool
- unStrmDgramEnabled bool
- rawSocketSess bool
+ stackOnce sync.Once
+ ipv4Enabled bool
+ canListenTCP4OnLoopback bool
+ ipv6Enabled bool
+ canListenTCP6OnLoopback bool
+ unStrmDgramEnabled bool
+ rawSocketSess bool
aLongTimeAgo = time.Unix(233431200, 0)
neverTimeout = time.Time{}
@@ -34,13 +36,19 @@ var (
)
func probeStack() {
+ if _, err := RoutedInterface("ip4", net.FlagUp); err == nil {
+ ipv4Enabled = true
+ }
if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
ln.Close()
- ipv4Enabled = true
+ canListenTCP4OnLoopback = true
+ }
+ if _, err := RoutedInterface("ip6", net.FlagUp); err == nil {
+ ipv6Enabled = true
}
if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
ln.Close()
- ipv6Enabled = true
+ canListenTCP6OnLoopback = true
}
rawSocketSess = supportsRawSocket()
switch runtime.GOOS {
@@ -154,22 +162,23 @@ func TestableAddress(network, address string) bool {
// The provided network must be "tcp", "tcp4", "tcp6", "unix" or
// "unixpacket".
func NewLocalListener(network string) (net.Listener, error) {
+ stackOnce.Do(probeStack)
switch network {
case "tcp":
- if SupportsIPv4() {
+ if canListenTCP4OnLoopback {
if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
return ln, nil
}
}
- if SupportsIPv6() {
+ if canListenTCP6OnLoopback {
return net.Listen("tcp6", "[::1]:0")
}
case "tcp4":
- if SupportsIPv4() {
+ if canListenTCP4OnLoopback {
return net.Listen("tcp4", "127.0.0.1:0")
}
case "tcp6":
- if SupportsIPv6() {
+ if canListenTCP6OnLoopback {
return net.Listen("tcp6", "[::1]:0")
}
case "unix", "unixpacket":
@@ -187,22 +196,23 @@ func NewLocalListener(network string) (net.Listener, error) {
//
// The provided network must be "udp", "udp4", "udp6" or "unixgram".
func NewLocalPacketListener(network string) (net.PacketConn, error) {
+ stackOnce.Do(probeStack)
switch network {
case "udp":
- if SupportsIPv4() {
+ if canListenTCP4OnLoopback {
if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
return c, nil
}
}
- if SupportsIPv6() {
+ if canListenTCP6OnLoopback {
return net.ListenPacket("udp6", "[::1]:0")
}
case "udp4":
- if SupportsIPv4() {
+ if canListenTCP4OnLoopback {
return net.ListenPacket("udp4", "127.0.0.1:0")
}
case "udp6":
- if SupportsIPv6() {
+ if canListenTCP6OnLoopback {
return net.ListenPacket("udp6", "[::1]:0")
}
case "unixgram":
diff --git a/netutil/listen.go b/netutil/listen.go
index d5dfbab24f..f8b779ea27 100644
--- a/netutil/listen.go
+++ b/netutil/listen.go
@@ -29,7 +29,7 @@ type limitListener struct {
}
// acquire acquires the limiting semaphore. Returns true if successfully
-// accquired, false if the listener is closed and the semaphore is not
+// acquired, false if the listener is closed and the semaphore is not
// acquired.
func (l *limitListener) acquire() bool {
select {
diff --git a/publicsuffix/data/children b/publicsuffix/data/children
new file mode 100644
index 0000000000..1038c561ad
Binary files /dev/null and b/publicsuffix/data/children differ
diff --git a/publicsuffix/data/nodes b/publicsuffix/data/nodes
new file mode 100644
index 0000000000..34751cd5b9
Binary files /dev/null and b/publicsuffix/data/nodes differ
diff --git a/publicsuffix/data/text b/publicsuffix/data/text
new file mode 100644
index 0000000000..124dcd61f4
--- /dev/null
+++ b/publicsuffix/data/text
@@ -0,0 +1 @@
+billustrationionjukudoyamakeupowiathletajimageandsoundandvision-riopretobishimagentositecnologiabiocelotenkawabipanasonicatfoodnetworkinggroupperbirdartcenterprisecloudaccesscamdvrcampaniabirkenesoddtangenovarahkkeravjuegoshikikiraraholtalenishikatakazakindependent-revieweirbirthplaceu-1bitbucketrzynishikatsuragirlyuzawabitternidiscoverybjarkoybjerkreimdbaltimore-og-romsdalp1bjugnishikawazukamishihoronobeautydalwaysdatabaseballangenkainanaejrietisalatinabenogatabitorderblackfridaybloombergbauernishimerabloxcms3-website-us-west-2blushakotanishinomiyashironocparachutingjovikarateu-2bmoattachmentsalangenishinoomotegovtattoolforgerockartuzybmsalon-1bmwellbeingzoneu-3bnrwesteuropenairbusantiquesaltdalomzaporizhzhedmarkaratsuginamikatagamilanotairesistanceu-4bondigitaloceanspacesaludishangrilanciabonnishinoshimatsusakahoginankokubunjindianapolis-a-bloggerbookonlinewjerseyboomlahppiacenzachpomorskienishiokoppegardiskussionsbereichattanooganordkapparaglidinglassassinationalheritageu-north-1boschaefflerdalondonetskarelianceu-south-1bostik-serveronagasukevje-og-hornnesalvadordalibabalatinord-aurdalipaywhirlondrinaplesknsalzburgleezextraspace-to-rentalstomakomaibarabostonakijinsekikogentappssejnyaarparalleluxembourglitcheltenham-radio-opensocialorenskogliwicebotanicalgardeno-staginglobodoes-itcouldbeworldisrechtranakamurataiwanairforcechireadthedocsxeroxfinitybotanicgardenishitosashimizunaminamiawajikindianmarketinglogowestfalenishiwakindielddanuorrindigenamsskoganeindustriabotanyanagawallonieruchomoscienceandindustrynissandiegoddabouncemerckmsdnipropetrovskjervoyageorgeorgiabounty-fullensakerrypropertiesamegawaboutiquebecommerce-shopselectaxihuanissayokkaichintaifun-dnsaliasamnangerboutireservditchyouriparasiteboyfriendoftheinternetflixjavaldaostathellevangerbozen-sudtirolottokorozawabozen-suedtirolouvreisenissedalovepoparisor-fronisshingucciprianiigataipeidsvollovesickariyakumodumeloyalistoragebplaceducatorprojectcmembersampalermomahaccapooguybrandywinevalleybrasiliadboxosascoli-picenorddalpusercontentcp4bresciaokinawashirosatobamagazineuesamsclubartowestus2brindisibenikitagataikikuchikumagayagawalmartgorybristoloseyouriparliamentjeldsundivtasvuodnakaniikawatanagurabritishcolumbialowiezaganiyodogawabroadcastlebtimnetzlgloomy-routerbroadwaybroke-itvedestrandivttasvuotnakanojohanamakindlefrakkestadiybrokerbrothermesaverdeatnulmemergencyachtsamsungloppennebrowsersafetymarketsandnessjoenl-ams-1brumunddalublindesnesandoybrunelastxn--0trq7p7nnbrusselsandvikcoromantovalle-daostavangerbruxellesanfranciscofreakunekobayashikaoirmemorialucaniabryanskodjedugit-pagespeedmobilizeroticagliaricoharuovatlassian-dev-builderscbglugsjcbnpparibashkiriabrynewmexicoacharterbuzzwfarmerseinebwhalingmbhartiffany-2bzhitomirbzzcodyn-vpndnsantacruzsantafedjeffersoncoffeedbackdropocznordlandrudupontariobranconavstackasaokamikoaniikappudownloadurbanamexhibitioncogretakamatsukawacollectioncolognewyorkshirebungoonordre-landurhamburgrimstadynamisches-dnsantamariakecolonialwilliamsburgripeeweeklylotterycoloradoplateaudnedalncolumbusheycommunexus-3community-prochowicecomobaravendbambleborkapsicilyonagoyauthgear-stagingivestbyglandroverhallair-traffic-controlleyombomloabaths-heilbronnoysunddnslivegarsheiheijibigawaustraliaustinnfshostrolekamisatokaizukameyamatotakadaustevollivornowtv-infolldalolipopmcdircompanychipstmncomparemarkerryhotelsantoandrepbodynaliasnesoddenmarkhangelskjakdnepropetrovskiervaapsteigenflfannefrankfurtjxn--12cfi8ixb8lutskashibatakashimarshallstatebankashiharacomsecaaskimitsubatamibuildingriwatarailwaycondoshichinohealth-carereformemsettlersanukindustriesteamfamberlevagangaviikanonjinfinitigotembaixadaconferenceconstructionconsuladogadollsaobernardomniweatherchanneluxuryconsultanthropologyconsultingroks-thisayamanobeokakegawacontactkmaxxn--12co0c3b4evalled-aostamayukinsuregruhostingrondarcontagematsubaravennaharimalborkashiwaracontemporaryarteducationalchikugodonnakaiwamizawashtenawsmppl-wawdev-myqnapcloudcontrolledogawarabikomaezakirunoopschlesischesaogoncartoonartdecologiacontractorskenconventureshinodearthickashiwazakiyosatokamachilloutsystemscloudsitecookingchannelsdvrdnsdojogaszkolancashirecifedexetercoolblogdnsfor-better-thanawassamukawatarikuzentakatairavpagecooperativano-frankivskygearapparochernigovernmentksatxn--1ck2e1bananarepublic-inquiryggeebinatsukigatajimidsundevelopmentatarantours3-external-1copenhagencyclopedichiropracticatholicaxiashorokanaiecoproductionsaotomeinforumzcorporationcorsicahcesuoloanswatch-and-clockercorvettenrissagaeroclubmedecincinnativeamericanantiquest-le-patron-k3sapporomuracosenzamamidorittoeigersundynathomebuiltwithdarkasserverrankoshigayaltakasugaintelligencecosidnshome-webservercellikescandypoppdaluzerncostumedicallynxn--1ctwolominamatargets-itlon-2couchpotatofriesardegnarutomobegetmyiparsardiniacouncilvivanovoldacouponsarlcozoracq-acranbrookuwanalyticsarpsborgrongausdalcrankyowariasahikawatchandclockasukabeauxartsandcraftsarufutsunomiyawakasaikaitabashijonawatecrdyndns-at-homedepotaruinterhostsolutionsasayamatta-varjjatmpartinternationalfirearmsaseboknowsitallcreditcardyndns-at-workshoppingrossetouchigasakitahiroshimansionsaskatchewancreditunioncremonashgabadaddjaguarqcxn--1lqs03ncrewhmessinarashinomutashinaintuitoyosatoyokawacricketnedalcrimeast-kazakhstanangercrotonecrownipartsassarinuyamashinazawacrsaudacruisesauheradyndns-blogsitextilegnicapetownnews-stagingroundhandlingroznycuisinellancasterculturalcentertainmentoyotapartysvardocuneocupcakecuritibabymilk3curvallee-d-aosteinkjerusalempresashibetsurugashimaringatlantajirinvestmentsavannahgacutegirlfriendyndns-freeboxoslocalzonecymrulvikasumigaurawa-mazowszexnetlifyinzairtrafficplexus-1cyonabarumesswithdnsaveincloudyndns-homednsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacyouthruherecipescaracaltanissettaishinomakilovecollegefantasyleaguernseyfembetsukumiyamazonawsglobalacceleratorahimeshimabaridagawatchesciencecentersciencehistoryfermockasuyamegurownproviderferraraferraris-a-catererferrerotikagoshimalopolskanlandyndns-picsaxofetsundyndns-remotewdyndns-ipasadenaroyfgujoinvilleitungsenfhvalerfidontexistmein-iservschulegallocalhostrodawarafieldyndns-serverdalfigueresindevicenzaolkuszczytnoipirangalsaceofilateliafilegear-augustowhoswholdingsmall-webthingscientistordalfilegear-debianfilegear-gbizfilegear-iefilegear-jpmorganfilegear-sg-1filminamiechizenfinalfinancefineartscrapper-sitefinlandyndns-weblikes-piedmonticellocus-4finnoyfirebaseappaviancarrdyndns-wikinkobearalvahkijoetsuldalvdalaskanittedallasalleasecuritytacticschoenbrunnfirenetoystre-slidrettozawafirenzefirestonefirewebpaascrappingulenfirmdaleikangerfishingoldpoint2thisamitsukefitjarvodkafjordyndns-workangerfitnessettlementozsdellogliastradingunmanxn--1qqw23afjalerfldrvalleeaosteflekkefjordyndns1flesberguovdageaidnunjargaflickragerogerscrysecretrosnubar0flierneflirfloginlinefloppythonanywhereggio-calabriafloraflorencefloridatsunangojomedicinakamagayahabackplaneapplinzis-a-celticsfanfloripadoval-daostavalleyfloristanohatakahamalselvendrellflorokunohealthcareerscwienflowerservehalflifeinsurancefltrani-andria-barletta-trani-andriaflynnhosting-clusterfnchiryukyuragifuchungbukharanzanfndynnschokokekschokoladenfnwkaszubytemarkatowicefoolfor-ourfor-somedio-campidano-mediocampidanomediofor-theaterforexrothachijolsterforgotdnservehttpbin-butterforli-cesena-forlicesenaforlillesandefjordynservebbscholarshipschoolbusinessebyforsaleirfjordynuniversityforsandasuolodingenfortalfortefortmissoulangevagrigentomologyeonggiehtavuoatnagahamaroygardencowayfortworthachinoheavyfosneservehumourfotraniandriabarlettatraniandriafoxfordecampobassociatest-iserveblogsytemp-dnserveirchitachinakagawashingtondchernivtsiciliafozfr-par-1fr-par-2franamizuhobby-sitefrancaiseharafranziskanerimalvikatsushikabedzin-addrammenuorochesterfredrikstadtvserveminecraftranoyfreeddnsfreebox-oservemp3freedesktopfizerfreemasonryfreemyiphosteurovisionfreesitefreetlservep2pgfoggiafreiburgushikamifuranorfolkebibleksvikatsuyamarugame-hostyhostingxn--2m4a15efrenchkisshikirkeneservepicservequakefreseniuscultureggio-emilia-romagnakasatsunairguardiannakadomarinebraskaunicommbankaufentigerfribourgfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganservesarcasmatartanddesignfrognfrolandynv6from-akrehamnfrom-alfrom-arfrom-azurewebsiteshikagamiishibukawakepnoorfrom-capitalonewportransipharmacienservicesevastopolefrom-coalfrom-ctranslatedynvpnpluscountryestateofdelawareclaimschoolsztynsettsupportoyotomiyazakis-a-candidatefrom-dchitosetodayfrom-dediboxafrom-flandersevenassisienarvikautokeinoticeablewismillerfrom-gaulardalfrom-hichisochikuzenfrom-iafrom-idyroyrvikingruenoharafrom-ilfrom-in-berlindasewiiheyaizuwakamatsubushikusakadogawafrom-ksharpharmacyshawaiijimarcheapartmentshellaspeziafrom-kyfrom-lanshimokawafrom-mamurogawatsonfrom-mdfrom-medizinhistorischeshimokitayamattelekommunikationfrom-mifunefrom-mnfrom-modalenfrom-mshimonitayanagit-reposts-and-telecommunicationshimonosekikawafrom-mtnfrom-nchofunatoriginstantcloudfrontdoorfrom-ndfrom-nefrom-nhktistoryfrom-njshimosuwalkis-a-chefarsundyndns-mailfrom-nminamifuranofrom-nvalleedaostefrom-nynysagamiharafrom-ohdattorelayfrom-oketogolffanshimotsukefrom-orfrom-padualstackazoologicalfrom-pratogurafrom-ris-a-conservativegashimotsumayfirstockholmestrandfrom-schmidtre-gauldalfrom-sdscloudfrom-tnfrom-txn--2scrj9chonanbunkyonanaoshimakanegasakikugawaltervistailscaleforcefrom-utsiracusaikirovogradoyfrom-vald-aostarostwodzislawildlifestylefrom-vtransportefrom-wafrom-wiardwebview-assetshinichinanfrom-wvanylvenneslaskerrylogisticshinjournalismartlabelingfrom-wyfrosinonefrostalowa-wolawafroyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairkitapps-auction-rancherkasydneyfujinomiyadattowebhoptogakushimotoganefujiokayamandalfujisatoshonairlinedre-eikerfujisawafujishiroishidakabiratoridedyn-berlincolnfujitsuruokazakiryuohkurafujiyoshidavvenjargap-east-1fukayabeardubaiduckdnsncfdfukuchiyamadavvesiidappnodebalancertmgrazimutheworkpccwilliamhillfukudomigawafukuis-a-cpalacefukumitsubishigakisarazure-mobileirvikazteleportlligatransurlfukuokakamigaharafukuroishikarikaturindalfukusakishiwadazaifudaigokaseljordfukuyamagatakaharunusualpersonfunabashiriuchinadafunagatakahashimamakisofukushimangonnakatombetsumy-gatewayfunahashikamiamakusatsumasendaisenergyfundaciofunkfeuerfuoiskujukuriyamangyshlakasamatsudoomdnstracefuosskoczowinbar1furubirafurudonostiaafurukawajimaniwakuratefusodegaurafussaintlouis-a-anarchistoireggiocalabriafutabayamaguchinomihachimanagementrapaniizafutboldlygoingnowhere-for-morenakatsugawafuttsurutaharafuturecmshinjukumamotoyamashikefuturehostingfuturemailingfvghamurakamigoris-a-designerhandcraftedhandsonyhangglidinghangoutwentehannanmokuizumodenaklodzkochikuseihidorahannorthwesternmutualhanyuzenhapmircloudletshintokushimahappounzenharvestcelebrationhasamap-northeast-3hasaminami-alpshintomikasaharahashbangryhasudahasura-apphiladelphiaareadmyblogspotrdhasvikfh-muensterhatogayahoooshikamaishimofusartshinyoshitomiokamisunagawahatoyamazakitakatakanabeatshiojirishirifujiedahatsukaichikaiseiyoichimkentrendhostinghattfjelldalhayashimamotobusellfylkesbiblackbaudcdn-edgestackhero-networkisboringhazuminobushistoryhelplfinancialhelsinkitakyushuaiahembygdsforbundhemneshioyanaizuerichardlimanowarudahemsedalhepforgeblockshirahamatonbetsurgeonshalloffameiwamasoyheroyhetemlbfanhgtvaohigashiagatsumagoianiahigashichichibuskerudhigashihiroshimanehigashiizumozakitamigrationhigashikagawahigashikagurasoedahigashikawakitaaikitamotosunndalhigashikurumeeresinstaginghigashimatsushimarburghigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshirakokonoehigashinarusells-for-lesshiranukamitondabayashiogamagoriziahigashinehigashiomitamanortonsberghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitanakagusukumodernhigashitsunosegawahigashiurausukitashiobarahigashiyamatokoriyamanashifteditorxn--30rr7yhigashiyodogawahigashiyoshinogaris-a-doctorhippyhiraizumisatohnoshoohirakatashinagawahiranairportland-4-salernogiessennanjobojis-a-financialadvisor-aurdalhirarahiratsukaerusrcfastlylbanzaicloudappspotagerhirayaitakaokalmykiahistorichouseshiraois-a-geekhakassiahitachiomiyagildeskaliszhitachiotagonohejis-a-greenhitraeumtgeradegreehjartdalhjelmelandholeckodairaholidayholyhomegoodshiraokamitsuehomeiphilatelyhomelinkyard-cloudjiffyresdalhomelinuxn--32vp30hachiojiyahikobierzycehomeofficehomesecuritymacaparecidahomesecuritypchoseikarugamvikarlsoyhomesenseeringhomesklepphilipsynology-diskstationhomeunixn--3bst00minamiiserniahondahongooglecodebergentinghonjyoitakarazukaluganskharkivaporcloudhornindalhorsells-for-ustkanmakiwielunnerhortendofinternet-dnshiratakahagitapphoenixn--3ds443ghospitalhoteleshishikuis-a-guruhotelwithflightshisognehotmailhoyangerhoylandetakasagophonefosshisuifuettertdasnetzhumanitieshitaramahungryhurdalhurumajis-a-hard-workershizukuishimogosenhyllestadhyogoris-a-hunterhyugawarahyundaiwafuneis-into-carsiiitesilkharkovaresearchaeologicalvinklein-the-bandairtelebitbridgestoneenebakkeshibechambagricultureadymadealstahaugesunderseaportsinfolionetworkdalaheadjudygarlandis-into-cartoonsimple-urlis-into-gamesserlillyis-leetrentin-suedtirolis-lostre-toteneis-a-lawyeris-not-certifiedis-savedis-slickhersonis-uberleetrentino-a-adigeis-very-badajozis-a-liberalis-very-evillageis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandovre-eikerisleofmanaustdaljellybeanjenv-arubahccavuotnagaragusabaerobaticketsirdaljeonnamerikawauejetztrentino-aadigejevnakershusdecorativeartslupskhmelnytskyivarggatrentino-alto-adigejewelryjewishartgalleryjfkhplaystation-cloudyclusterjgorajlljls-sto1jls-sto2jls-sto3jmphotographysiojnjaworznospamproxyjoyentrentino-altoadigejoyokaichibajddarchitecturealtorlandjpnjprslzjurkotohiradomainstitutekotourakouhokutamamurakounosupabasembokukizunokunimilitarykouyamarylhurstjordalshalsenkouzushimasfjordenkozagawakozakis-a-llamarnardalkozowindowskrakowinnersnoasakatakkokamiminersokndalkpnkppspbarcelonagawakkanaibetsubamericanfamilyds3-fips-us-gov-west-1krasnikahokutokashikis-a-musiciankrasnodarkredstonekrelliankristiansandcatsolarssonkristiansundkrodsheradkrokstadelvalle-aostatic-accessolognekryminamiizukaminokawanishiaizubangekumanotteroykumatorinovecoregontrailroadkumejimashikis-a-nascarfankumenantokonamegatakatoris-a-nursells-itrentin-sud-tirolkunisakis-a-painteractivelvetrentin-sudtirolkunitachiaraindropilotsolundbecknx-serversellsyourhomeftphxn--3e0b707ekunitomigusukuleuvenetokigawakunneppuboliviajessheimpertrixcdn77-secureggioemiliaromagnamsosnowiechristiansburgminakamichiharakunstsammlungkunstunddesignkuokgroupimientaketomisatoolsomakurehabmerkurgankurobeeldengeluidkurogimimatakatsukis-a-patsfankuroisoftwarezzoologykuromatsunais-a-personaltrainerkuronkurotakikawasakis-a-photographerokussldkushirogawakustanais-a-playershiftcryptonomichigangwonkusupersalezajskomakiyosemitekutchanelkutnowruzhgorodeokuzumakis-a-republicanonoichinomiyakekvafjordkvalsundkvamscompute-1kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsomnatalkzmisakis-a-soxfanmisasaguris-a-studentalmisawamisconfusedmishimasudamissilemisugitokuyamatsumaebashikshacknetrentino-sued-tirolmitakeharamitourismilemitoyoakemiuramiyazurecontainerdpolicemiyotamatsukuris-a-teacherkassyno-dshowamjondalenmonstermontrealestatefarmequipmentrentino-suedtirolmonza-brianzapposor-odalmonza-e-della-brianzaptokyotangotpantheonsitemonzabrianzaramonzaebrianzamonzaedellabrianzamoonscalebookinghostedpictetrentinoa-adigemordoviamoriyamatsumotofukemoriyoshiminamiashigaramormonmouthachirogatakamoriokakudamatsuemoroyamatsunomortgagemoscowiosor-varangermoseushimodatemosjoenmoskenesorfoldmossorocabalena-devicesorreisahayakawakamiichikawamisatottoris-a-techietis-a-landscaperspectakasakitchenmosvikomatsushimarylandmoteginowaniihamatamakinoharamoviemovimientolgamozilla-iotrentinoaadigemtranbytomaritimekeepingmuginozawaonsensiositemuikaminoyamaxunispacemukoebenhavnmulhouseoullensvanguardmunakatanemuncienciamuosattemupinbarclaycards3-sa-east-1murmanskomforbar2murotorcraftrentinoalto-adigemusashinoharamuseetrentinoaltoadigemuseumverenigingmusicargodaddyn-o-saurlandesortlandmutsuzawamy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoruminamimakis-a-rockstarachowicemydattolocalcertificationmyddnsgeekgalaxymydissentrentinos-tirolmydobissmarterthanyoumydrobofageologymydsoundcastronomy-vigorlicemyeffectrentinostirolmyfastly-terrariuminamiminowamyfirewalledreplittlestargardmyforuminamioguni5myfritzmyftpaccessouthcarolinaturalhistorymuseumcentermyhome-servermyjinomykolaivencloud66mymailermymediapchristmasakillucernemyokohamamatsudamypepinkommunalforbundmypetsouthwest1-uslivinghistorymyphotoshibalashovhadanorth-kazakhstanmypicturestaurantrentinosud-tirolmypsxn--3pxu8kommunemysecuritycamerakermyshopblocksowamyshopifymyspreadshopwarendalenugmythic-beastspectruminamisanrikubetsuppliesoomytis-a-bookkeepermaritimodspeedpartnermytuleap-partnersphinxn--41amyvnchromediatechnologymywirepaircraftingvollohmusashimurayamashikokuchuoplantationplantspjelkavikomorotsukagawaplatformsharis-a-therapistoiaplatter-appinokofuefukihaboromskogplatterpioneerplazaplcube-serversicherungplumbingoplurinacionalpodhalepodlasiellaktyubinskiptveterinairealmpmnpodzonepohlpoivronpokerpokrovskomvuxn--3hcrj9choyodobashichikashukujitawaraumalatvuopmicrosoftbankarmoypoliticarrierpolitiendapolkowicepoltavalle-d-aostaticspydebergpomorzeszowitdkongsbergponpesaro-urbino-pesarourbinopesaromasvuotnarusawapordenonepornporsangerporsangugeporsgrunnanyokoshibahikariwanumatakinouepoznanpraxis-a-bruinsfanprdpreservationpresidioprgmrprimetelemarkongsvingerprincipeprivatizehealthinsuranceprofesionalprogressivestfoldpromombetsupplypropertyprotectionprotonetrentinosued-tirolprudentialpruszkowithgoogleapiszprvcyberprzeworskogpulawypunyufuelveruminamiuonumassa-carrara-massacarraramassabuyshousesopotrentino-sud-tirolpupugliapussycateringebuzentsujiiepvhadselfiphdfcbankazunoticiashinkamigototalpvtrentinosuedtirolpwchungnamdalseidsbergmodellingmxn--11b4c3dray-dnsupdaterpzqhaebaruericssongdalenviknakayamaoris-a-cubicle-slavellinodeobjectshinshinotsurfashionstorebaselburguidefinimamateramochizukimobetsumidatlantichirurgiens-dentistes-en-franceqldqotoyohashimotoshimatsuzakis-an-accountantshowtimelbourneqponiatowadaqslgbtrentinsud-tirolqualifioappippueblockbusternopilawaquickconnectrentinsudtirolquicksytesrhtrentinsued-tirolquipelementsrltunestuff-4-saletunkonsulatrobeebyteappigboatsmolaquilanxessmushcdn77-sslingturystykaniepcetuscanytushuissier-justicetuvalleaostaverntuxfamilytwmailvestvagoyvevelstadvibo-valentiavibovalentiavideovillastufftoread-booksnestorfjordvinnicasadelamonedagestangevinnytsiavipsinaappiwatevirginiavirtual-uservecounterstrikevirtualcloudvirtualservervirtualuserveexchangevirtuelvisakuhokksundviterbolognagasakikonaikawagoevivianvivolkenkundenvixn--42c2d9avlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavminanovologdanskonyveloftrentino-stirolvolvolkswagentstuttgartrentinsuedtirolvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiecircustomer-ocimmobilienwixsitewloclawekoobindalwmcloudwmflabsurnadalwoodsidelmenhorstabackyardsurreyworse-thandawowithyoutuberspacekitagawawpdevcloudwpenginepoweredwphostedmailwpmucdnpixolinodeusercontentrentinosudtirolwpmudevcdnaccessokanagawawritesthisblogoipizzawroclawiwatsukiyonoshiroomgwtcirclerkstagewtfastvps-serverisignwuozuwzmiuwajimaxn--4gbriminingxn--4it168dxn--4it797kooris-a-libertarianxn--4pvxs4allxn--54b7fta0ccivilaviationredumbrellajollamericanexpressexyxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49civilisationrenderxn--5rtq34koperviklabudhabikinokawachinaganoharamcocottempurlxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264civilizationthewifiatmallorcafederation-webspacexn--80aaa0cvacationsusonoxn--80adxhksuzakananiimiharuxn--80ao21axn--80aqecdr1axn--80asehdbarclays3-us-east-2xn--80aswgxn--80aukraanghkembuchikujobservableusercontentrevisohughestripperxn--8dbq2axn--8ltr62koryokamikawanehonbetsuwanouchijiwadeliveryxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisenbahnxn--90a3academiamicable-modemoneyxn--90aeroportalabamagasakishimabaraffleentry-snowplowiczeladzxn--90aishobarakawaharaoxn--90amckinseyxn--90azhytomyrxn--9dbhblg6dietritonxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byandexcloudxn--asky-iraxn--aurskog-hland-jnbarefootballooningjerstadgcapebretonamicrolightingjesdalombardiadembroideryonagunicloudiherokuappanamasteiermarkaracoldwarszawauthgearappspacehosted-by-previderxn--avery-yuasakuragawaxn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsuzukanazawaxn--bck1b9a5dre4civilwarmiasadoesntexisteingeekarpaczest-a-la-maisondre-landrayddns5yxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyaotsurgeryxn--bjddar-ptargithubpreviewsaitohmannore-og-uvdalxn--blt-elabourxn--bmlo-graingerxn--bod-2naturalsciencesnaturellesuzukis-an-actorxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-acornxn--brum-voagatroandinosaureportrentoyonakagyokutoyakomaganexn--btsfjord-9zaxn--bulsan-sdtirol-nsbaremetalpha-myqnapcloud9guacuiababia-goracleaningitpagexlimoldell-ogliastraderxn--c1avgxn--c2br7gxn--c3s14mincomcastreserve-onlinexn--cck2b3bargainstances3-us-gov-west-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-an-actresshwindmillxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes-v6a2oxn--correios-e-telecomunicaes-ghc29axn--czr694barreaudiblebesbydgoszczecinemagnethnologyoriikaragandauthordalandroiddnss3-ap-southeast-2ix4432-balsan-suedtirolimiteddnskinggfakefurniturecreationavuotnaritakoelnayorovigotsukisosakitahatakahatakaishimoichinosekigaharaurskog-holandingitlaborxn--czrs0trogstadxn--czru2dxn--czrw28barrel-of-knowledgeappgafanquanpachicappacificurussiautomotivelandds3-ca-central-16-balsan-sudtirollagdenesnaaseinet-freaks3-ap-southeast-123websiteleaf-south-123webseiteckidsmynasushiobarackmazerbaijan-mayen-rootaribeiraogakibichuobiramusementdllpages3-ap-south-123sitewebhareidfjordvagsoyerhcloudd-dnsiskinkyolasiteastcoastaldefenceastus2038xn--d1acj3barrell-of-knowledgecomputerhistoryofscience-fictionfabricafjs3-us-west-1xn--d1alfaromeoxn--d1atromsakegawaxn--d5qv7z876clanbibaidarmeniaxn--davvenjrga-y4axn--djrs72d6uyxn--djty4kosaigawaxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmailukowhitesnow-dnsangohtawaramotoineppubtlsanjotelulubin-brbambinagisobetsuitagajoburgjerdrumcprequalifymein-vigorgebetsukuibmdeveloperauniteroizumizakinderoyomitanobninskanzakiyokawaraustrheimatunduhrennebulsan-suedtirololitapunk123kotisivultrobjectselinogradimo-siemenscaledekaascolipiceno-ipifony-1337xn--eckvdtc9dxn--efvn9svalbardunloppaderbornxn--efvy88hagakhanamigawaxn--ehqz56nxn--elqq16hagebostadxn--eveni-0qa01gaxn--f6qx53axn--fct429kosakaerodromegallupaasdaburxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsvchurchaseljeepsondriodejaneirockyotobetsuliguriaxn--fiq64barsycenterprisesakievennodesadistcgrouplidlugolekagaminord-frontierxn--fiqs8sveioxn--fiqz9svelvikoninjambylxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbssvizzeraxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grandrapidsvn-repostorjcloud-ver-jpchowderxn--frna-woaraisaijosoyroroswedenxn--frya-hraxn--fzc2c9e2cleverappsannanxn--fzys8d69uvgmailxn--g2xx48clicketcloudcontrolapparmatsuuraxn--gckr3f0fauskedsmokorsetagayaseralingenoamishirasatogliattipschulserverxn--gecrj9clickrisinglesannohekinannestadraydnsanokaruizawaxn--ggaviika-8ya47haibarakitakamiizumisanofidelitysfjordxn--gildeskl-g0axn--givuotna-8yasakaiminatoyookaneyamazoexn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-an-anarchistoricalsocietysnesigdalxn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45br5cylxn--gnstigliefern-wobihirosakikamijimatsushigexn--h-2failxn--h1aeghair-surveillancexn--h1ahnxn--h1alizxn--h2breg3eveneswidnicasacampinagrandebungotakadaemongolianxn--h2brj9c8clinichippubetsuikilatironporterxn--h3cuzk1digickoseis-a-linux-usershoujis-a-knightpointtohoboleslawieconomiastalbanshizuokamogawaxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinewhampshirealtychyattorneyagawakuyabukihokumakogeniwaizumiotsurugimbalsfjordeportexaskoyabeagleboardetroitskypecorivneatonoshoes3-eu-west-3utilitiesquare7xn--hebda8basicserversaillesjabbottateshinanomachildrensgardenhlfanhsbc66xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-aptibleangaviikadenaamesjevuemielnoboribetsuckswidnikkolobrzegersundxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyasugithubusercontentromsojamisonxn--io0a7is-an-artistgstagexn--j1adpkomonotogawaxn--j1aefbsbxn--1lqs71dyndns-office-on-the-webhostingrpassagensavonarviikamiokameokamakurazakiwakunigamihamadaxn--j1ael8basilicataniautoscanadaeguambulancentralus-2xn--j1amhakatanorthflankddiamondshinshiroxn--j6w193gxn--jlq480n2rgxn--jlq61u9w7basketballfinanzgorzeleccodespotenzakopanewspaperxn--jlster-byasuokannamihokkaidopaaskvollxn--jrpeland-54axn--jvr189miniserversusakis-a-socialistg-builderxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45brj9cistrondheimperiaxn--koluokta-7ya57hakodatexn--kprw13dxn--kpry57dxn--kput3is-an-engineeringxn--krager-gyatominamibosogndalxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdevcloudfunctionsimplesitexn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsukanoyakagexn--kvnangen-k0axn--l-1fairwindswiebodzin-dslattuminamiyamashirokawanabeepilepsykkylvenicexn--l1accentureklamborghinikolaeventswinoujscienceandhistoryxn--laheadju-7yatsushiroxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52batochigifts3-us-west-2xn--lesund-huaxn--lgbbat1ad8jdfaststackschulplattformetacentrumeteorappassenger-associationxn--lgrd-poacctrusteexn--lhppi-xqaxn--linds-pramericanartrvestnestudioxn--lns-qlavagiskexn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacliniquedapliexn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddeswisstpetersburgxn--mgb9awbfbx-ostrowwlkpmguitarschwarzgwangjuifminamidaitomanchesterxn--mgba3a3ejtrycloudflarevistaplestudynamic-dnsrvaroyxn--mgba3a4f16axn--mgba3a4fra1-deloittevaksdalxn--mgba7c0bbn0axn--mgbaakc7dvfstdlibestadxn--mgbaam7a8hakonexn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscordsays3-website-ap-northeast-1xn--mgbai9azgqp6jejuniperxn--mgbayh7gpalmaseratis-an-entertainerxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskosherbrookegawaxn--mgbqly7c0a67fbclintonkotsukubankarumaifarmsteadrobaknoluoktachikawakayamadridvallee-aosteroyxn--mgbqly7cvafr-1xn--mgbt3dhdxn--mgbtf8flapymntrysiljanxn--mgbtx2bauhauspostman-echocolatemasekd1xn--mgbx4cd0abbvieeexn--mix082fbxoschweizxn--mix891fedorainfraclouderaxn--mjndalen-64axn--mk0axin-vpnclothingdustdatadetectjmaxxxn--12c1fe0bradescotlandrrxn--mk1bu44cn-northwest-1xn--mkru45is-bykleclerchoshibuyachiyodancexn--mlatvuopmi-s4axn--mli-tlavangenxn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-certifiedxn--mosjen-eyawaraxn--mot-tlazioxn--mre-og-romsdal-qqbuseranishiaritakurashikis-foundationxn--msy-ula0hakubaghdadultravelchannelxn--mtta-vrjjat-k7aflakstadaokagakicks-assnasaarlandxn--muost-0qaxn--mxtq1minisitexn--ngbc5azdxn--ngbe9e0axn--ngbrxn--45q11citadelhicampinashikiminohostfoldnavyxn--nit225koshimizumakiyosunnydayxn--nmesjevuemie-tcbalestrandabergamoarekeymachineustarnbergxn--nnx388axn--nodessakyotanabelaudiopsysynology-dstreamlitappittsburghofficialxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeserveftplanetariuminamitanexn--nvuotna-hwaxn--nyqy26axn--o1achernihivgubsxn--o3cw4hakuis-a-democratravelersinsurancexn--o3cyx2axn--od0algxn--od0aq3belementorayoshiokanumazuryukuhashimojibxos3-website-ap-southeast-1xn--ogbpf8flatangerxn--oppegrd-ixaxn--ostery-fyawatahamaxn--osyro-wuaxn--otu796dxn--p1acfedorapeoplegoismailillehammerfeste-ipatriaxn--p1ais-gonexn--pgbs0dhlx3xn--porsgu-sta26fedoraprojectoyotsukaidoxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cngreaterxn--qcka1pmcpenzaporizhzhiaxn--qqqt11minnesotaketakayamassivegridxn--qxa6axn--qxamsterdamnserverbaniaxn--rady-iraxn--rdal-poaxn--rde-ulaxn--rdy-0nabaris-into-animeetrentin-sued-tirolxn--rennesy-v1axn--rhkkervju-01afeiraquarelleasingujaratoyouraxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturbruksgymnxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byaxn--rny31hakusanagochihayaakasakawaiishopitsitexn--rovu88bellevuelosangeles3-website-ap-southeast-2xn--rros-granvindafjordxn--rskog-uuaxn--rst-0naturhistorischesxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byaxn--s-1faithaldenxn--s9brj9cnpyatigorskolecznagatorodoyxn--sandnessjen-ogbellunord-odalombardyn53xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4dbgdty6citichernovtsyncloudrangedaluccarbonia-iglesias-carboniaiglesiascarboniaxn--skierv-utazasxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5natuurwetenschappenginexn--slt-elabcieszynh-servebeero-stageiseiroumuenchencoreapigeelvinckoshunantankmpspawnextdirectrentino-s-tirolxn--smla-hraxn--smna-gratangentlentapisa-geekosugexn--snase-nraxn--sndre-land-0cbeneventochiokinoshimaintenancebinordreisa-hockeynutazurestaticappspaceusercontentateyamaveroykenglandeltaitogitsumitakagiizeasypanelblagrarchaeologyeongbuk0emmafann-arboretumbriamallamaceiobbcg123homepagefrontappchizip61123minsidaarborteaches-yogasawaracingroks-theatree123hjemmesidealerimo-i-rana4u2-localhistorybolzano-altoadigeometre-experts-comptables3-ap-northeast-123miwebcambridgehirn4t3l3p0rtarumizusawabogadobeaemcloud-fr123paginaweberkeleyokosukanrabruzzombieidskoguchikushinonsenasakuchinotsuchiurakawafaicloudineat-url-o-g-i-naval-d-aosta-valleyokote164-b-datacentermezproxyzgoraetnabudejjudaicadaquest-mon-blogueurodirumaceratabuseating-organicbcn-north-123saitamakawabartheshopencraftrainingdyniajuedischesapeakebayernavigationavoi234lima-cityeats3-ap-northeast-20001wwwedeployokozeastasiamunemurorangecloudplatform0xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbentleyurihonjournalistjohnikonanporovnobserverxn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bulls-fanxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbeppublishproxyusuharavocatanzarowegroweiboltashkentatamotorsitestingivingjemnes3-eu-central-1kappleadpages-12hpalmspringsakerxn--stre-toten-zcbeskidyn-ip24xn--t60b56axn--tckweddingxn--tiq49xqyjelasticbeanstalkhmelnitskiyamarumorimachidaxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbestbuyshoparenagareyamaizurugbyenvironmentalconservationflashdrivefsnillfjordiscordsezjampaleoceanographics3-website-eu-west-1xn--trentin-sdtirol-7vbetainaboxfuseekloges3-website-sa-east-1xn--trentino-sd-tirol-c3bhzcasertainaioirasebastopologyeongnamegawafflecellclstagemologicaliforniavoues3-eu-west-1xn--trentino-sdtirol-szbielawalbrzycharitypedreamhostersvp4xn--trentinosd-tirol-rzbiellaakesvuemieleccebizenakanotoddeninoheguriitatebayashiibahcavuotnagaivuotnagaokakyotambabybluebitelevisioncilla-speziaxarnetbank8s3-eu-west-2xn--trentinosdtirol-7vbieszczadygeyachimataijiiyamanouchikuhokuryugasakitaurayasudaxn--trentinsd-tirol-6vbievat-band-campaignieznombrendlyngengerdalces3-website-us-east-1xn--trentinsdtirol-nsbifukagawalesundiscountypeformelhusgardeninomiyakonojorpelandiscourses3-website-us-west-1xn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvestre-slidrexn--uc0ay4axn--uist22halsakakinokiaxn--uisz3gxn--unjrga-rtarnobrzegyptianxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtularvikonskowolayangroupiemontexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccerxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbigvalledaostaobaomoriguchiharag-cloud-championshiphoplixboxenirasakincheonishiazaindependent-commissionishigouvicasinordeste-idclkarasjohkamikitayamatsurindependent-inquest-a-la-masionishiharaxn--vestvgy-ixa6oxn--vg-yiabkhaziaxn--vgan-qoaxn--vgsy-qoa0jelenia-goraxn--vgu402cnsantabarbaraxn--vhquvestre-totennishiawakuraxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861biharstadotsubetsugaruhrxn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1cntjomeldaluroyxn--wgbl6axn--xhq521bihorologyusuisservegame-serverxn--xkc2al3hye2axn--xkc2dl3a5ee0hammarfeastafricaravantaaxn--y9a3aquariumintereitrentino-sudtirolxn--yer-znaumburgxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4dbrk0cexn--ystre-slidre-ujbikedaejeonbukarasjokarasuyamarriottatsunoceanographiquehimejindependent-inquiryuufcfanishiizunazukindependent-panelomoliseminemrxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bilbaogashimadachicagoboavistanbulsan-sudtirolbia-tempio-olbiatempioolbialystokkeliwebredirectme-south-1xnbayxz
\ No newline at end of file
diff --git a/publicsuffix/gen.go b/publicsuffix/gen.go
index 9e6c91878b..2ad0abdc1a 100644
--- a/publicsuffix/gen.go
+++ b/publicsuffix/gen.go
@@ -22,6 +22,7 @@ package main
import (
"bufio"
"bytes"
+ "encoding/binary"
"flag"
"fmt"
"go/format"
@@ -55,6 +56,7 @@ const (
)
var (
+ combinedText string
maxChildren int
maxTextOffset int
maxTextLength int
@@ -115,11 +117,10 @@ var (
shaRE = regexp.MustCompile(`"sha":"([^"]+)"`)
dateRE = regexp.MustCompile(`"committer":{[^{]+"date":"([^"]+)"`)
- comments = flag.Bool("comments", false, "generate table.go comments, for debugging")
- subset = flag.Bool("subset", false, "generate only a subset of the full table, for debugging")
- url = flag.String("url", defaultURL, "URL of the publicsuffix.org list. If empty, stdin is read instead")
- v = flag.Bool("v", false, "verbose output (to stderr)")
- version = flag.String("version", "", "the effective_tld_names.dat version")
+ subset = flag.Bool("subset", false, "generate only a subset of the full table, for debugging")
+ url = flag.String("url", defaultURL, "URL of the publicsuffix.org list. If empty, stdin is read instead")
+ v = flag.Bool("v", false, "verbose output (to stderr)")
+ version = flag.String("version", "", "the effective_tld_names.dat version")
)
func main() {
@@ -254,7 +255,33 @@ func main1() error {
}
sort.Strings(labelsList)
- if err := generate(printReal, &root, "table.go"); err != nil {
+ combinedText = combineText(labelsList)
+ if combinedText == "" {
+ return fmt.Errorf("internal error: combineText returned no text")
+ }
+ for _, label := range labelsList {
+ offset, length := strings.Index(combinedText, label), len(label)
+ if offset < 0 {
+ return fmt.Errorf("internal error: could not find %q in text %q", label, combinedText)
+ }
+ maxTextOffset, maxTextLength = max(maxTextOffset, offset), max(maxTextLength, length)
+ if offset >= 1<= 1<= 0; i -= 8 {
+ b = append(b, byte((encoding>>i)&0xff))
+ }
+ return b
+}
+
+func printMetadata(w io.Writer, n *node) error {
const header = `// generated by go run gen.go; DO NOT EDIT
package publicsuffix
+import _ "embed"
+
const version = %q
const (
@@ -343,74 +415,36 @@ const (
// numTLD is the number of top level domains.
const numTLD = %d
+// text is the combined text of all labels.
+//
+//go:embed data/text
+var text string
+
`
fmt.Fprintf(w, header, *version,
nodesBits,
nodesBitsChildren, nodesBitsICANN, nodesBitsTextOffset, nodesBitsTextLength,
childrenBitsWildcard, childrenBitsNodeType, childrenBitsHi, childrenBitsLo,
nodeTypeNormal, nodeTypeException, nodeTypeParentOnly, len(n.children))
-
- text := combineText(labelsList)
- if text == "" {
- return fmt.Errorf("internal error: makeText returned no text")
- }
- for _, label := range labelsList {
- offset, length := strings.Index(text, label), len(label)
- if offset < 0 {
- return fmt.Errorf("internal error: could not find %q in text %q", label, text)
- }
- maxTextOffset, maxTextLength = max(maxTextOffset, offset), max(maxTextLength, length)
- if offset >= 1<= 1< 64 {
- n, plus = 64, " +"
- }
- fmt.Fprintf(w, "%q%s\n", text[:n], plus)
- text = text[n:]
- }
-
- if err := n.walk(w, assignIndexes); err != nil {
- return err
- }
-
fmt.Fprintf(w, `
-
// nodes is the list of nodes. Each node is represented as a %v-bit integer,
// which encodes the node's children, wildcard bit and node type (as an index
// into the children array), ICANN bit and text.
//
-// If the table was generated with the -comments flag, there is a //-comment
-// after each node's data. In it is the nodes-array indexes of the children,
-// formatted as (n0x1234-n0x1256), with * denoting the wildcard bit. The
-// nodeType is printed as + for normal, ! for exception, and o for parent-only
-// nodes that have children but don't match a domain label in their own right.
-// An I denotes an ICANN domain.
-//
// The layout within the node, from MSB to LSB, is:
// [%2d bits] unused
// [%2d bits] children index
// [%2d bits] ICANN bit
// [%2d bits] text index
// [%2d bits] text length
-var nodes = [...]uint8{
+//
+//go:embed data/nodes
+var nodes uint40String
`,
nodesBits,
nodesBits-nodesBitsChildren-nodesBitsICANN-nodesBitsTextOffset-nodesBitsTextLength,
nodesBitsChildren, nodesBitsICANN, nodesBitsTextOffset, nodesBitsTextLength)
- if err := n.walk(w, printNode); err != nil {
- return err
- }
- fmt.Fprintf(w, `}
-
+ fmt.Fprintf(w, `
// children is the list of nodes' children, the parent's wildcard bit and the
// parent's node type. If a node has no children then their children index
// will be in the range [0, 6), depending on the wildcard bit and node type.
@@ -421,27 +455,13 @@ var nodes = [...]uint8{
// [%2d bits] node type
// [%2d bits] high nodes index (exclusive) of children
// [%2d bits] low nodes index (inclusive) of children
-var children=[...]uint32{
+//
+//go:embed data/children
+var children uint32String
`,
32-childrenBitsWildcard-childrenBitsNodeType-childrenBitsHi-childrenBitsLo,
childrenBitsWildcard, childrenBitsNodeType, childrenBitsHi, childrenBitsLo)
- for i, c := range childrenEncoding {
- s := "---------------"
- lo := c & (1<> childrenBitsLo) & (1<>(childrenBitsLo+childrenBitsHi)) & (1<>(childrenBitsLo+childrenBitsHi+childrenBitsNodeType) != 0
- if *comments {
- fmt.Fprintf(w, "0x%08x, // c0x%04x (%s)%s %s\n",
- c, i, s, wildcardStr(wildcard), nodeTypeStr(nodeType))
- } else {
- fmt.Fprintf(w, "0x%x,\n", c)
- }
- }
- fmt.Fprintf(w, "}\n\n")
+
fmt.Fprintf(w, "// max children %d (capacity %d)\n", maxChildren, 1<= 0; i -= 8 {
- fmt.Fprintf(w, "0x%02x, ", (encoding>>i)&0xff)
- }
- if *comments {
- fmt.Fprintf(w, "// n0x%04x c0x%04x (%s)%s %s %s %s\n",
- c.nodesIndex, c.childrenIndex, s, wildcardStr(c.wildcard),
- nodeTypeStr(c.nodeType), icannStr(c.icann), c.label,
- )
- } else {
- fmt.Fprintf(w, "\n")
- }
- }
- return nil
-}
-
func printNodeLabel(w io.Writer, n *node) error {
for _, c := range n.children {
fmt.Fprintf(w, "%q,\n", c.label)
diff --git a/publicsuffix/list.go b/publicsuffix/list.go
index 7caeeaa696..d56e9e7624 100644
--- a/publicsuffix/list.go
+++ b/publicsuffix/list.go
@@ -101,10 +101,10 @@ loop:
break
}
- u := uint32(nodeValue(f) >> (nodesBitsTextOffset + nodesBitsTextLength))
+ u := uint32(nodes.get(f) >> (nodesBitsTextOffset + nodesBitsTextLength))
icannNode = u&(1<>= nodesBitsICANN
- u = children[u&(1<>= childrenBitsLo
hi = u & (1<>= nodesBitsTextLength
offset := x & (1<