mirror of
https://github.com/rakyll/hey.git
synced 2026-10-01 23:35:17 +08:00
go mod tidy and go mod vendor
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
module github.com/rakyll/hey
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
||||
require golang.org/x/net v0.48.0
|
||||
|
||||
go 1.13
|
||||
require golang.org/x/text v0.33.0 // indirect
|
||||
|
||||
go 1.24.0
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko=
|
||||
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
|
||||
+39
-38
@@ -12,7 +12,7 @@ import (
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
var isTokenTable = [127]bool{
|
||||
var isTokenTable = [256]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
@@ -93,12 +93,7 @@ var isTokenTable = [127]bool{
|
||||
}
|
||||
|
||||
func IsTokenRune(r rune) bool {
|
||||
i := int(r)
|
||||
return i < len(isTokenTable) && isTokenTable[i]
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !IsTokenRune(r)
|
||||
return r < utf8.RuneSelf && isTokenTable[byte(r)]
|
||||
}
|
||||
|
||||
// HeaderValuesContainsToken reports whether any string in values
|
||||
@@ -137,11 +132,13 @@ func trimOWS(x string) string {
|
||||
// contains token amongst its comma-separated tokens, ASCII
|
||||
// case-insensitively.
|
||||
func headerValueContainsToken(v string, token string) bool {
|
||||
v = trimOWS(v)
|
||||
if comma := strings.IndexByte(v, ','); comma != -1 {
|
||||
return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token)
|
||||
for comma := strings.IndexByte(v, ','); comma != -1; comma = strings.IndexByte(v, ',') {
|
||||
if tokenEqual(trimOWS(v[:comma]), token) {
|
||||
return true
|
||||
}
|
||||
v = v[comma+1:]
|
||||
}
|
||||
return tokenEqual(v, token)
|
||||
return tokenEqual(trimOWS(v), token)
|
||||
}
|
||||
|
||||
// lowerASCII returns the ASCII lowercase version of b.
|
||||
@@ -171,13 +168,15 @@ func tokenEqual(t1, t2 string) bool {
|
||||
|
||||
// isLWS reports whether b is linear white space, according
|
||||
// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
//
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
func isLWS(b byte) bool { return b == ' ' || b == '\t' }
|
||||
|
||||
// isCTL reports whether b is a control byte, according
|
||||
// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
|
||||
// CTL = <any US-ASCII control character
|
||||
// (octets 0 - 31) and DEL (127)>
|
||||
//
|
||||
// CTL = <any US-ASCII control character
|
||||
// (octets 0 - 31) and DEL (127)>
|
||||
func isCTL(b byte) bool {
|
||||
const del = 0x7f // a CTL
|
||||
return b < ' ' || b == del
|
||||
@@ -187,18 +186,19 @@ func isCTL(b byte) bool {
|
||||
// HTTP/2 imposes the additional restriction that uppercase ASCII
|
||||
// letters are not allowed.
|
||||
//
|
||||
// RFC 7230 says:
|
||||
// header-field = field-name ":" OWS field-value OWS
|
||||
// field-name = token
|
||||
// token = 1*tchar
|
||||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
|
||||
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
||||
// RFC 7230 says:
|
||||
//
|
||||
// header-field = field-name ":" OWS field-value OWS
|
||||
// field-name = token
|
||||
// token = 1*tchar
|
||||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
|
||||
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
||||
func ValidHeaderFieldName(v string) bool {
|
||||
if len(v) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, r := range v {
|
||||
if !IsTokenRune(r) {
|
||||
for i := 0; i < len(v); i++ {
|
||||
if !isTokenTable[v[i]] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -265,27 +265,28 @@ var validHostByte = [256]bool{
|
||||
// ValidHeaderFieldValue reports whether v is a valid "field-value" according to
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 :
|
||||
//
|
||||
// message-header = field-name ":" [ field-value ]
|
||||
// field-value = *( field-content | LWS )
|
||||
// field-content = <the OCTETs making up the field-value
|
||||
// and consisting of either *TEXT or combinations
|
||||
// of token, separators, and quoted-string>
|
||||
// message-header = field-name ":" [ field-value ]
|
||||
// field-value = *( field-content | LWS )
|
||||
// field-content = <the OCTETs making up the field-value
|
||||
// and consisting of either *TEXT or combinations
|
||||
// of token, separators, and quoted-string>
|
||||
//
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 :
|
||||
//
|
||||
// TEXT = <any OCTET except CTLs,
|
||||
// but including LWS>
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// CTL = <any US-ASCII control character
|
||||
// (octets 0 - 31) and DEL (127)>
|
||||
// TEXT = <any OCTET except CTLs,
|
||||
// but including LWS>
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// CTL = <any US-ASCII control character
|
||||
// (octets 0 - 31) and DEL (127)>
|
||||
//
|
||||
// RFC 7230 says:
|
||||
// field-value = *( field-content / obs-fold )
|
||||
// obj-fold = N/A to http2, and deprecated
|
||||
// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
// field-vchar = VCHAR / obs-text
|
||||
// obs-text = %x80-FF
|
||||
// VCHAR = "any visible [USASCII] character"
|
||||
//
|
||||
// field-value = *( field-content / obs-fold )
|
||||
// obj-fold = N/A to http2, and deprecated
|
||||
// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
// field-vchar = VCHAR / obs-text
|
||||
// obs-text = %x80-FF
|
||||
// VCHAR = "any visible [USASCII] character"
|
||||
//
|
||||
// http2 further says: "Similarly, HTTP/2 allows header field values
|
||||
// that are not valid. While most of the values that can be encoded
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
#
|
||||
# This Dockerfile builds a recent curl with HTTP/2 client support, using
|
||||
# a recent nghttp2 build.
|
||||
#
|
||||
# See the Makefile for how to tag it. If Docker and that image is found, the
|
||||
# Go tests use this curl binary for integration tests.
|
||||
#
|
||||
|
||||
FROM ubuntu:trusty
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install -y git-core build-essential wget
|
||||
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
autotools-dev libtool pkg-config zlib1g-dev \
|
||||
libcunit1-dev libssl-dev libxml2-dev libevent-dev \
|
||||
automake autoconf
|
||||
|
||||
# The list of packages nghttp2 recommends for h2load:
|
||||
RUN apt-get install -y --no-install-recommends make binutils \
|
||||
autoconf automake autotools-dev \
|
||||
libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \
|
||||
libev-dev libevent-dev libjansson-dev libjemalloc-dev \
|
||||
cython python3.4-dev python-setuptools
|
||||
|
||||
# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
|
||||
ENV NGHTTP2_VER 895da9a
|
||||
RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
|
||||
|
||||
WORKDIR /root/nghttp2
|
||||
RUN git reset --hard $NGHTTP2_VER
|
||||
RUN autoreconf -i
|
||||
RUN automake
|
||||
RUN autoconf
|
||||
RUN ./configure
|
||||
RUN make
|
||||
RUN make install
|
||||
|
||||
WORKDIR /root
|
||||
RUN wget http://curl.haxx.se/download/curl-7.45.0.tar.gz
|
||||
RUN tar -zxvf curl-7.45.0.tar.gz
|
||||
WORKDIR /root/curl-7.45.0
|
||||
RUN ./configure --with-ssl --with-nghttp2=/usr/local
|
||||
RUN make
|
||||
RUN make install
|
||||
RUN ldconfig
|
||||
|
||||
CMD ["-h"]
|
||||
ENTRYPOINT ["/usr/local/bin/curl"]
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
curlimage:
|
||||
docker build -t gohttp2/curl .
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
This is a work-in-progress HTTP/2 implementation for Go.
|
||||
|
||||
It will eventually live in the Go standard library and won't require
|
||||
any changes to your code to use. It will just be automatic.
|
||||
|
||||
Status:
|
||||
|
||||
* The server support is pretty good. A few things are missing
|
||||
but are being worked on.
|
||||
* The client work has just started but shares a lot of code
|
||||
is coming along much quicker.
|
||||
|
||||
Docs are at https://godoc.org/golang.org/x/net/http2
|
||||
|
||||
Demo test server at https://http2.golang.org/
|
||||
|
||||
Help & bug reports welcome!
|
||||
|
||||
Contributing: https://golang.org/doc/contribute.html
|
||||
Bugs: https://golang.org/issue/new?title=x/net/http2:+
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import "strings"
|
||||
|
||||
// The HTTP protocols are defined in terms of ASCII, not Unicode. This file
|
||||
// contains helper functions which may use Unicode-aware functions which would
|
||||
// otherwise be unsafe and could introduce vulnerabilities if used improperly.
|
||||
|
||||
// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
|
||||
// are equal, ASCII-case-insensitively.
|
||||
func asciiEqualFold(s, t string) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if lower(s[i]) != lower(t[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lower returns the ASCII lowercase version of b.
|
||||
func lower(b byte) byte {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
return b + ('a' - 'A')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// isASCIIPrint returns whether s is ASCII and printable according to
|
||||
// https://tools.ietf.org/html/rfc20#section-4.2.
|
||||
func isASCIIPrint(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < ' ' || s[i] > '~' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiToLower returns the lowercase version of s if s is ASCII and printable,
|
||||
// and whether or not it was.
|
||||
func asciiToLower(s string) (lower string, ok bool) {
|
||||
if !isASCIIPrint(s) {
|
||||
return "", false
|
||||
}
|
||||
return strings.ToLower(s), true
|
||||
}
|
||||
+83
-54
@@ -7,13 +7,21 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||
type ClientConnPool interface {
|
||||
// GetClientConn returns a specific HTTP/2 connection (usually
|
||||
// a TLS-TCP connection) to an HTTP/2 server. On success, the
|
||||
// returned ClientConn accounts for the upcoming RoundTrip
|
||||
// call, so the caller should not omit it. If the caller needs
|
||||
// to, ClientConn.RoundTrip can be called with a bogus
|
||||
// new(http.Request) to release the stream reservation.
|
||||
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||
MarkDead(*ClientConn)
|
||||
}
|
||||
@@ -40,7 +48,7 @@ type clientConnPool struct {
|
||||
conns map[string][]*ClientConn // key is host:port
|
||||
dialing map[string]*dialCall // currently in-flight dials
|
||||
keys map[*ClientConn][]string
|
||||
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
|
||||
addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls
|
||||
}
|
||||
|
||||
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
@@ -52,87 +60,85 @@ const (
|
||||
noDialOnMiss = false
|
||||
)
|
||||
|
||||
// shouldTraceGetConn reports whether getClientConn should call any
|
||||
// ClientTrace.GetConn hook associated with the http.Request.
|
||||
//
|
||||
// This complexity is needed to avoid double calls of the GetConn hook
|
||||
// during the back-and-forth between net/http and x/net/http2 (when the
|
||||
// net/http.Transport is upgraded to also speak http2), as well as support
|
||||
// the case where x/net/http2 is being used directly.
|
||||
func (p *clientConnPool) shouldTraceGetConn(st clientConnIdleState) bool {
|
||||
// If our Transport wasn't made via ConfigureTransport, always
|
||||
// trace the GetConn hook if provided, because that means the
|
||||
// http2 package is being used directly and it's the one
|
||||
// dialing, as opposed to net/http.
|
||||
if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok {
|
||||
return true
|
||||
}
|
||||
// Otherwise, only use the GetConn hook if this connection has
|
||||
// been used previously for other requests. For fresh
|
||||
// connections, the net/http package does the dialing.
|
||||
return !st.freshConn
|
||||
}
|
||||
|
||||
func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
|
||||
// TODO(dneil): Dial a new connection when t.DisableKeepAlives is set?
|
||||
if isConnectionCloseRequest(req) && dialOnMiss {
|
||||
// It gets its own connection.
|
||||
traceGetConn(req, addr)
|
||||
const singleUse = true
|
||||
cc, err := p.t.dialClientConn(addr, singleUse)
|
||||
cc, err := p.t.dialClientConn(req.Context(), addr, singleUse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
for _, cc := range p.conns[addr] {
|
||||
if st := cc.idleState(); st.canTakeNewRequest {
|
||||
if p.shouldTraceGetConn(st) {
|
||||
traceGetConn(req, addr)
|
||||
for {
|
||||
p.mu.Lock()
|
||||
for _, cc := range p.conns[addr] {
|
||||
if cc.ReserveNewRequest() {
|
||||
// When a connection is presented to us by the net/http package,
|
||||
// the GetConn hook has already been called.
|
||||
// Don't call it a second time here.
|
||||
if !cc.getConnCalled {
|
||||
traceGetConn(req, addr)
|
||||
}
|
||||
cc.getConnCalled = false
|
||||
p.mu.Unlock()
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
if !dialOnMiss {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrNoCachedConn
|
||||
}
|
||||
traceGetConn(req, addr)
|
||||
call := p.getStartDialLocked(req.Context(), addr)
|
||||
p.mu.Unlock()
|
||||
<-call.done
|
||||
if shouldRetryDial(call, req) {
|
||||
continue
|
||||
}
|
||||
cc, err := call.res, call.err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cc.ReserveNewRequest() {
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
if !dialOnMiss {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrNoCachedConn
|
||||
}
|
||||
traceGetConn(req, addr)
|
||||
call := p.getStartDialLocked(addr)
|
||||
p.mu.Unlock()
|
||||
<-call.done
|
||||
return call.res, call.err
|
||||
}
|
||||
|
||||
// dialCall is an in-flight Transport dial call to a host.
|
||||
type dialCall struct {
|
||||
p *clientConnPool
|
||||
_ incomparable
|
||||
p *clientConnPool
|
||||
// the context associated with the request
|
||||
// that created this dialCall
|
||||
ctx context.Context
|
||||
done chan struct{} // closed when done
|
||||
res *ClientConn // valid after done is closed
|
||||
err error // valid after done is closed
|
||||
}
|
||||
|
||||
// requires p.mu is held.
|
||||
func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
|
||||
func (p *clientConnPool) getStartDialLocked(ctx context.Context, addr string) *dialCall {
|
||||
if call, ok := p.dialing[addr]; ok {
|
||||
// A dial is already in-flight. Don't start another.
|
||||
return call
|
||||
}
|
||||
call := &dialCall{p: p, done: make(chan struct{})}
|
||||
call := &dialCall{p: p, done: make(chan struct{}), ctx: ctx}
|
||||
if p.dialing == nil {
|
||||
p.dialing = make(map[string]*dialCall)
|
||||
}
|
||||
p.dialing[addr] = call
|
||||
go call.dial(addr)
|
||||
go call.dial(call.ctx, addr)
|
||||
return call
|
||||
}
|
||||
|
||||
// run in its own goroutine.
|
||||
func (c *dialCall) dial(addr string) {
|
||||
func (c *dialCall) dial(ctx context.Context, addr string) {
|
||||
const singleUse = false // shared conn
|
||||
c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
|
||||
close(c.done)
|
||||
c.res, c.err = c.p.t.dialClientConn(ctx, addr, singleUse)
|
||||
|
||||
c.p.mu.Lock()
|
||||
delete(c.p.dialing, addr)
|
||||
@@ -140,6 +146,8 @@ func (c *dialCall) dial(addr string) {
|
||||
c.p.addConnLocked(addr, c.res)
|
||||
}
|
||||
c.p.mu.Unlock()
|
||||
|
||||
close(c.done)
|
||||
}
|
||||
|
||||
// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
|
||||
@@ -150,7 +158,7 @@ func (c *dialCall) dial(addr string) {
|
||||
// This code decides which ones live or die.
|
||||
// The return value used is whether c was used.
|
||||
// c is never closed.
|
||||
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
|
||||
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c net.Conn) (used bool, err error) {
|
||||
p.mu.Lock()
|
||||
for _, cc := range p.conns[key] {
|
||||
if cc.CanTakeNewRequest() {
|
||||
@@ -180,19 +188,21 @@ func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn)
|
||||
}
|
||||
|
||||
type addConnCall struct {
|
||||
_ incomparable
|
||||
p *clientConnPool
|
||||
done chan struct{} // closed when done
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||
cc, err := t.NewClientConn(tc)
|
||||
func (c *addConnCall) run(t *Transport, key string, nc net.Conn) {
|
||||
cc, err := t.NewClientConn(nc)
|
||||
|
||||
p := c.p
|
||||
p.mu.Lock()
|
||||
if err != nil {
|
||||
c.err = err
|
||||
} else {
|
||||
cc.getConnCalled = true // already called by the net/http package
|
||||
p.addConnLocked(key, cc)
|
||||
}
|
||||
delete(p.addConnCalls, key)
|
||||
@@ -200,12 +210,6 @@ func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||
close(c.done)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) addConn(key string, cc *ClientConn) {
|
||||
p.mu.Lock()
|
||||
p.addConnLocked(key, cc)
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// p.mu must be held
|
||||
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||
for _, v := range p.conns[key] {
|
||||
@@ -280,3 +284,28 @@ type noDialClientConnPool struct{ *clientConnPool }
|
||||
func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
return p.getClientConn(req, addr, noDialOnMiss)
|
||||
}
|
||||
|
||||
// shouldRetryDial reports whether the current request should
|
||||
// retry dialing after the call finished unsuccessfully, for example
|
||||
// if the dial was canceled because of a context cancellation or
|
||||
// deadline expiry.
|
||||
func shouldRetryDial(call *dialCall, req *http.Request) bool {
|
||||
if call.err == nil {
|
||||
// No error, no need to retry
|
||||
return false
|
||||
}
|
||||
if call.ctx == req.Context() {
|
||||
// If the call has the same context as the request, the dial
|
||||
// should not be retried, since any cancellation will have come
|
||||
// from this request.
|
||||
return false
|
||||
}
|
||||
if !errors.Is(call.err, context.Canceled) && !errors.Is(call.err, context.DeadlineExceeded) {
|
||||
// If the call error is not because of a context cancellation or a deadline expiry,
|
||||
// the dial should not be retried.
|
||||
return false
|
||||
}
|
||||
// Only retry if the error is a context cancellation error or deadline expiry
|
||||
// and the context associated with the call was canceled or expired.
|
||||
return call.ctx.Err() != nil
|
||||
}
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http2Config is a package-internal version of net/http.HTTP2Config.
|
||||
//
|
||||
// http.HTTP2Config was added in Go 1.24.
|
||||
// When running with a version of net/http that includes HTTP2Config,
|
||||
// we merge the configuration with the fields in Transport or Server
|
||||
// to produce an http2Config.
|
||||
//
|
||||
// Zero valued fields in http2Config are interpreted as in the
|
||||
// net/http.HTTPConfig documentation.
|
||||
//
|
||||
// Precedence order for reconciling configurations is:
|
||||
//
|
||||
// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero.
|
||||
// - Otherwise use the http2.{Server.Transport} value.
|
||||
// - If the resulting value is zero or out of range, use a default.
|
||||
type http2Config struct {
|
||||
MaxConcurrentStreams uint32
|
||||
StrictMaxConcurrentRequests bool
|
||||
MaxDecoderHeaderTableSize uint32
|
||||
MaxEncoderHeaderTableSize uint32
|
||||
MaxReadFrameSize uint32
|
||||
MaxUploadBufferPerConnection int32
|
||||
MaxUploadBufferPerStream int32
|
||||
SendPingTimeout time.Duration
|
||||
PingTimeout time.Duration
|
||||
WriteByteTimeout time.Duration
|
||||
PermitProhibitedCipherSuites bool
|
||||
CountError func(errType string)
|
||||
}
|
||||
|
||||
// configFromServer merges configuration settings from
|
||||
// net/http.Server.HTTP2Config and http2.Server.
|
||||
func configFromServer(h1 *http.Server, h2 *Server) http2Config {
|
||||
conf := http2Config{
|
||||
MaxConcurrentStreams: h2.MaxConcurrentStreams,
|
||||
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
|
||||
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
|
||||
MaxReadFrameSize: h2.MaxReadFrameSize,
|
||||
MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection,
|
||||
MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream,
|
||||
SendPingTimeout: h2.ReadIdleTimeout,
|
||||
PingTimeout: h2.PingTimeout,
|
||||
WriteByteTimeout: h2.WriteByteTimeout,
|
||||
PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites,
|
||||
CountError: h2.CountError,
|
||||
}
|
||||
fillNetHTTPConfig(&conf, h1.HTTP2)
|
||||
setConfigDefaults(&conf, true)
|
||||
return conf
|
||||
}
|
||||
|
||||
// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2
|
||||
// (the net/http Transport).
|
||||
func configFromTransport(h2 *Transport) http2Config {
|
||||
conf := http2Config{
|
||||
StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams,
|
||||
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
|
||||
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
|
||||
MaxReadFrameSize: h2.MaxReadFrameSize,
|
||||
SendPingTimeout: h2.ReadIdleTimeout,
|
||||
PingTimeout: h2.PingTimeout,
|
||||
WriteByteTimeout: h2.WriteByteTimeout,
|
||||
}
|
||||
|
||||
// Unlike most config fields, where out-of-range values revert to the default,
|
||||
// Transport.MaxReadFrameSize clips.
|
||||
if conf.MaxReadFrameSize < minMaxFrameSize {
|
||||
conf.MaxReadFrameSize = minMaxFrameSize
|
||||
} else if conf.MaxReadFrameSize > maxFrameSize {
|
||||
conf.MaxReadFrameSize = maxFrameSize
|
||||
}
|
||||
|
||||
if h2.t1 != nil {
|
||||
fillNetHTTPConfig(&conf, h2.t1.HTTP2)
|
||||
}
|
||||
setConfigDefaults(&conf, false)
|
||||
return conf
|
||||
}
|
||||
|
||||
func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) {
|
||||
if *v < minval || *v > maxval {
|
||||
*v = defval
|
||||
}
|
||||
}
|
||||
|
||||
func setConfigDefaults(conf *http2Config, server bool) {
|
||||
setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, defaultMaxStreams)
|
||||
setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize)
|
||||
setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize)
|
||||
if server {
|
||||
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20)
|
||||
} else {
|
||||
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow)
|
||||
}
|
||||
if server {
|
||||
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20)
|
||||
} else {
|
||||
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow)
|
||||
}
|
||||
setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize)
|
||||
setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second)
|
||||
}
|
||||
|
||||
// adjustHTTP1MaxHeaderSize converts a limit in bytes on the size of an HTTP/1 header
|
||||
// to an HTTP/2 MAX_HEADER_LIST_SIZE value.
|
||||
func adjustHTTP1MaxHeaderSize(n int64) int64 {
|
||||
// http2's count is in a slightly different unit and includes 32 bytes per pair.
|
||||
// So, take the net/http.Server value and pad it up a bit, assuming 10 headers.
|
||||
const perFieldOverhead = 32 // per http2 spec
|
||||
const typicalHeaders = 10 // conservative
|
||||
return n + typicalHeaders*perFieldOverhead
|
||||
}
|
||||
|
||||
func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) {
|
||||
if h2 == nil {
|
||||
return
|
||||
}
|
||||
if h2.MaxConcurrentStreams != 0 {
|
||||
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
|
||||
}
|
||||
if http2ConfigStrictMaxConcurrentRequests(h2) {
|
||||
conf.StrictMaxConcurrentRequests = true
|
||||
}
|
||||
if h2.MaxEncoderHeaderTableSize != 0 {
|
||||
conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize)
|
||||
}
|
||||
if h2.MaxDecoderHeaderTableSize != 0 {
|
||||
conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize)
|
||||
}
|
||||
if h2.MaxConcurrentStreams != 0 {
|
||||
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
|
||||
}
|
||||
if h2.MaxReadFrameSize != 0 {
|
||||
conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize)
|
||||
}
|
||||
if h2.MaxReceiveBufferPerConnection != 0 {
|
||||
conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection)
|
||||
}
|
||||
if h2.MaxReceiveBufferPerStream != 0 {
|
||||
conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream)
|
||||
}
|
||||
if h2.SendPingTimeout != 0 {
|
||||
conf.SendPingTimeout = h2.SendPingTimeout
|
||||
}
|
||||
if h2.PingTimeout != 0 {
|
||||
conf.PingTimeout = h2.PingTimeout
|
||||
}
|
||||
if h2.WriteByteTimeout != 0 {
|
||||
conf.WriteByteTimeout = h2.WriteByteTimeout
|
||||
}
|
||||
if h2.PermitProhibitedCipherSuites {
|
||||
conf.PermitProhibitedCipherSuites = true
|
||||
}
|
||||
if h2.CountError != nil {
|
||||
conf.CountError = h2.CountError
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.26
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
|
||||
return false
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.26
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
|
||||
return h2.StrictMaxConcurrentRequests
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func configureTransport(t1 *http.Transport) (*Transport, error) {
|
||||
connPool := new(clientConnPool)
|
||||
t2 := &Transport{
|
||||
ConnPool: noDialClientConnPool{connPool},
|
||||
t1: t1,
|
||||
}
|
||||
connPool.t = t2
|
||||
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t1.TLSClientConfig == nil {
|
||||
t1.TLSClientConfig = new(tls.Config)
|
||||
}
|
||||
if !strSliceContains(t1.TLSClientConfig.NextProtos, "h2") {
|
||||
t1.TLSClientConfig.NextProtos = append([]string{"h2"}, t1.TLSClientConfig.NextProtos...)
|
||||
}
|
||||
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
|
||||
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
|
||||
}
|
||||
upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
|
||||
addr := authorityAddr("https", authority)
|
||||
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
|
||||
go c.Close()
|
||||
return erringRoundTripper{err}
|
||||
} else if !used {
|
||||
// Turns out we don't need this c.
|
||||
// For example, two goroutines made requests to the same host
|
||||
// at the same time, both kicking off TCP dials. (since protocol
|
||||
// was unknown)
|
||||
go c.Close()
|
||||
}
|
||||
return t2
|
||||
}
|
||||
if m := t1.TLSNextProto; len(m) == 0 {
|
||||
t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
|
||||
"h2": upgradeFn,
|
||||
}
|
||||
} else {
|
||||
m["h2"] = upgradeFn
|
||||
}
|
||||
return t2, nil
|
||||
}
|
||||
|
||||
// registerHTTPSProtocol calls Transport.RegisterProtocol but
|
||||
// converting panics into errors.
|
||||
func registerHTTPSProtocol(t *http.Transport, rt noDialH2RoundTripper) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}()
|
||||
t.RegisterProtocol("https", rt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request
|
||||
// if there's already has a cached connection to the host.
|
||||
// (The field is exported so it can be accessed via reflect from net/http; tested
|
||||
// by TestNoDialH2RoundTripperType)
|
||||
type noDialH2RoundTripper struct{ *Transport }
|
||||
|
||||
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
res, err := rt.Transport.RoundTrip(req)
|
||||
if isNoCachedConnError(err) {
|
||||
return nil, http.ErrSkipAltProtocol
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
+31
-28
@@ -20,41 +20,44 @@ import (
|
||||
// TODO: Benchmark to determine if the pools are necessary. The GC may have
|
||||
// improved enough that we can instead allocate chunks like this:
|
||||
// make([]byte, max(16<<10, expectedBytesRemaining))
|
||||
var (
|
||||
dataChunkSizeClasses = []int{
|
||||
1 << 10,
|
||||
2 << 10,
|
||||
4 << 10,
|
||||
8 << 10,
|
||||
16 << 10,
|
||||
}
|
||||
dataChunkPools = [...]sync.Pool{
|
||||
{New: func() interface{} { return make([]byte, 1<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 2<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 4<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 8<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 16<<10) }},
|
||||
}
|
||||
)
|
||||
var dataChunkPools = [...]sync.Pool{
|
||||
{New: func() interface{} { return new([1 << 10]byte) }},
|
||||
{New: func() interface{} { return new([2 << 10]byte) }},
|
||||
{New: func() interface{} { return new([4 << 10]byte) }},
|
||||
{New: func() interface{} { return new([8 << 10]byte) }},
|
||||
{New: func() interface{} { return new([16 << 10]byte) }},
|
||||
}
|
||||
|
||||
func getDataBufferChunk(size int64) []byte {
|
||||
i := 0
|
||||
for ; i < len(dataChunkSizeClasses)-1; i++ {
|
||||
if size <= int64(dataChunkSizeClasses[i]) {
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case size <= 1<<10:
|
||||
return dataChunkPools[0].Get().(*[1 << 10]byte)[:]
|
||||
case size <= 2<<10:
|
||||
return dataChunkPools[1].Get().(*[2 << 10]byte)[:]
|
||||
case size <= 4<<10:
|
||||
return dataChunkPools[2].Get().(*[4 << 10]byte)[:]
|
||||
case size <= 8<<10:
|
||||
return dataChunkPools[3].Get().(*[8 << 10]byte)[:]
|
||||
default:
|
||||
return dataChunkPools[4].Get().(*[16 << 10]byte)[:]
|
||||
}
|
||||
return dataChunkPools[i].Get().([]byte)
|
||||
}
|
||||
|
||||
func putDataBufferChunk(p []byte) {
|
||||
for i, n := range dataChunkSizeClasses {
|
||||
if len(p) == n {
|
||||
dataChunkPools[i].Put(p)
|
||||
return
|
||||
}
|
||||
switch len(p) {
|
||||
case 1 << 10:
|
||||
dataChunkPools[0].Put((*[1 << 10]byte)(p))
|
||||
case 2 << 10:
|
||||
dataChunkPools[1].Put((*[2 << 10]byte)(p))
|
||||
case 4 << 10:
|
||||
dataChunkPools[2].Put((*[4 << 10]byte)(p))
|
||||
case 8 << 10:
|
||||
dataChunkPools[3].Put((*[8 << 10]byte)(p))
|
||||
case 16 << 10:
|
||||
dataChunkPools[4].Put((*[16 << 10]byte)(p))
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
|
||||
}
|
||||
|
||||
// dataBuffer is an io.ReadWriter backed by a list of data chunks.
|
||||
|
||||
+13
-1
@@ -53,6 +53,13 @@ func (e ErrCode) String() string {
|
||||
return fmt.Sprintf("unknown error code 0x%x", uint32(e))
|
||||
}
|
||||
|
||||
func (e ErrCode) stringToken() string {
|
||||
if s, ok := errCodeName[e]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("ERR_UNKNOWN_%d", uint32(e))
|
||||
}
|
||||
|
||||
// ConnectionError is an error that results in the termination of the
|
||||
// entire connection.
|
||||
type ConnectionError ErrCode
|
||||
@@ -67,6 +74,11 @@ type StreamError struct {
|
||||
Cause error // optional additional detail
|
||||
}
|
||||
|
||||
// errFromPeer is a sentinel error value for StreamError.Cause to
|
||||
// indicate that the StreamError was sent from the peer over the wire
|
||||
// and wasn't locally generated in the Transport.
|
||||
var errFromPeer = errors.New("received from peer")
|
||||
|
||||
func streamError(id uint32, code ErrCode) StreamError {
|
||||
return StreamError{StreamID: id, Code: code}
|
||||
}
|
||||
@@ -124,7 +136,7 @@ func (e headerFieldNameError) Error() string {
|
||||
type headerFieldValueError string
|
||||
|
||||
func (e headerFieldValueError) Error() string {
|
||||
return fmt.Sprintf("invalid header field value %q", string(e))
|
||||
return fmt.Sprintf("invalid header field value for %q", string(e))
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
+83
-13
@@ -6,21 +6,91 @@
|
||||
|
||||
package http2
|
||||
|
||||
// flow is the flow control window's size.
|
||||
type flow struct {
|
||||
// n is the number of DATA bytes we're allowed to send.
|
||||
// A flow is kept both on a conn and a per-stream.
|
||||
n int32
|
||||
// inflowMinRefresh is the minimum number of bytes we'll send for a
|
||||
// flow control window update.
|
||||
const inflowMinRefresh = 4 << 10
|
||||
|
||||
// conn points to the shared connection-level flow that is
|
||||
// shared by all streams on that conn. It is nil for the flow
|
||||
// that's on the conn directly.
|
||||
conn *flow
|
||||
// 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
|
||||
}
|
||||
|
||||
func (f *flow) setConnFlow(cf *flow) { f.conn = cf }
|
||||
// init sets the initial window.
|
||||
func (f *inflow) init(n int32) {
|
||||
f.avail = n
|
||||
}
|
||||
|
||||
func (f *flow) available() int32 {
|
||||
// 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.
|
||||
// An outflow is kept both on a conn and a per-stream.
|
||||
n int32
|
||||
|
||||
// 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 *outflow
|
||||
}
|
||||
|
||||
func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf }
|
||||
|
||||
func (f *outflow) available() int32 {
|
||||
n := f.n
|
||||
if f.conn != nil && f.conn.n < n {
|
||||
n = f.conn.n
|
||||
@@ -28,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")
|
||||
}
|
||||
@@ -40,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
|
||||
|
||||
+210
-71
@@ -23,7 +23,7 @@ const frameHeaderLen = 9
|
||||
var padZeros = make([]byte, 255) // zeros for padding
|
||||
|
||||
// A FrameType is a registered frame type as defined in
|
||||
// http://http2.github.io/http2-spec/#rfc.section.11.2
|
||||
// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2
|
||||
type FrameType uint8
|
||||
|
||||
const (
|
||||
@@ -39,7 +39,7 @@ const (
|
||||
FrameContinuation FrameType = 0x9
|
||||
)
|
||||
|
||||
var frameName = map[FrameType]string{
|
||||
var frameNames = [...]string{
|
||||
FrameData: "DATA",
|
||||
FrameHeaders: "HEADERS",
|
||||
FramePriority: "PRIORITY",
|
||||
@@ -53,10 +53,10 @@ var frameName = map[FrameType]string{
|
||||
}
|
||||
|
||||
func (t FrameType) String() string {
|
||||
if s, ok := frameName[t]; ok {
|
||||
return s
|
||||
if int(t) < len(frameNames) {
|
||||
return frameNames[t]
|
||||
}
|
||||
return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", uint8(t))
|
||||
return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", t)
|
||||
}
|
||||
|
||||
// Flags is a bitmask of HTTP/2 flags.
|
||||
@@ -122,9 +122,9 @@ var flagName = map[FrameType]map[Flags]string{
|
||||
// a frameParser parses a frame given its FrameHeader and payload
|
||||
// bytes. The length of payload will always equal fh.Length (which
|
||||
// might be 0).
|
||||
type frameParser func(fc *frameCache, fh FrameHeader, payload []byte) (Frame, error)
|
||||
type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error)
|
||||
|
||||
var frameParsers = map[FrameType]frameParser{
|
||||
var frameParsers = [...]frameParser{
|
||||
FrameData: parseDataFrame,
|
||||
FrameHeaders: parseHeadersFrame,
|
||||
FramePriority: parsePriorityFrame,
|
||||
@@ -138,15 +138,15 @@ var frameParsers = map[FrameType]frameParser{
|
||||
}
|
||||
|
||||
func typeFrameParser(t FrameType) frameParser {
|
||||
if f := frameParsers[t]; f != nil {
|
||||
return f
|
||||
if int(t) < len(frameParsers) {
|
||||
return frameParsers[t]
|
||||
}
|
||||
return parseUnknownFrame
|
||||
}
|
||||
|
||||
// A FrameHeader is the 9 byte header of all HTTP/2 frames.
|
||||
//
|
||||
// See http://http2.github.io/http2-spec/#FrameHeader
|
||||
// See https://httpwg.org/specs/rfc7540.html#FrameHeader
|
||||
type FrameHeader struct {
|
||||
valid bool // caller can access []byte fields in the Frame
|
||||
|
||||
@@ -225,6 +225,11 @@ var fhBytes = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
func invalidHTTP1LookingFrameHeader() FrameHeader {
|
||||
fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 "))
|
||||
return fh
|
||||
}
|
||||
|
||||
// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader.
|
||||
// Most users should use Framer.ReadFrame instead.
|
||||
func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
|
||||
@@ -267,9 +272,16 @@ type Framer struct {
|
||||
lastFrame Frame
|
||||
errDetail error
|
||||
|
||||
// countError is a non-nil func that's called on a frame parse
|
||||
// error with some unique error path token. It's initialized
|
||||
// from Transport.CountError or Server.CountError.
|
||||
countError func(errToken string)
|
||||
|
||||
// lastHeaderStream is non-zero if the last frame was an
|
||||
// unfinished HEADERS/CONTINUATION.
|
||||
lastHeaderStream uint32
|
||||
// lastFrameType holds the type of the last frame for verifying frame order.
|
||||
lastFrameType FrameType
|
||||
|
||||
maxReadSize uint32
|
||||
headerBuf [frameHeaderLen]byte
|
||||
@@ -337,7 +349,7 @@ func (fr *Framer) maxHeaderListSize() uint32 {
|
||||
func (f *Framer) startWrite(ftype FrameType, flags Flags, streamID uint32) {
|
||||
// Write the FrameHeader.
|
||||
f.wbuf = append(f.wbuf[:0],
|
||||
0, // 3 bytes of length, filled in in endWrite
|
||||
0, // 3 bytes of length, filled in endWrite
|
||||
0,
|
||||
0,
|
||||
byte(ftype),
|
||||
@@ -426,6 +438,7 @@ func NewFramer(w io.Writer, r io.Reader) *Framer {
|
||||
fr := &Framer{
|
||||
w: w,
|
||||
r: r,
|
||||
countError: func(string) {},
|
||||
logReads: logFrameReads,
|
||||
logWrites: logFrameWrites,
|
||||
debugReadLoggerf: log.Printf,
|
||||
@@ -477,39 +490,57 @@ func terminalReadFrameError(err error) bool {
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// ReadFrame reads a single frame. The returned Frame is only valid
|
||||
// until the next call to ReadFrame.
|
||||
// ReadFrameHeader reads the header of the next frame.
|
||||
// It reads the 9-byte fixed frame header, and does not read any portion of the
|
||||
// frame payload. The caller is responsible for consuming the payload, either
|
||||
// with ReadFrameForHeader or directly from the Framer's io.Reader.
|
||||
//
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, the
|
||||
// returned error is ErrFrameTooLarge. Other errors may be of type
|
||||
// ConnectionError, StreamError, or anything else from the underlying
|
||||
// reader.
|
||||
func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, it
|
||||
// returns the frame header and ErrFrameTooLarge.
|
||||
//
|
||||
// If the returned FrameHeader.StreamID is non-zero, it indicates the stream
|
||||
// responsible for the error.
|
||||
func (fr *Framer) ReadFrameHeader() (FrameHeader, error) {
|
||||
fr.errDetail = nil
|
||||
fh, err := readFrameHeader(fr.headerBuf[:], fr.r)
|
||||
if err != nil {
|
||||
return fh, err
|
||||
}
|
||||
if fh.Length > fr.maxReadSize {
|
||||
if fh == invalidHTTP1LookingFrameHeader() {
|
||||
return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge)
|
||||
}
|
||||
return fh, ErrFrameTooLarge
|
||||
}
|
||||
if err := fr.checkFrameOrder(fh); err != nil {
|
||||
return fh, err
|
||||
}
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
// ReadFrameForHeader reads the payload for the frame with the given FrameHeader.
|
||||
//
|
||||
// It behaves identically to ReadFrame, other than not checking the maximum
|
||||
// frame size.
|
||||
func (fr *Framer) ReadFrameForHeader(fh FrameHeader) (Frame, error) {
|
||||
if fr.lastFrame != nil {
|
||||
fr.lastFrame.invalidate()
|
||||
}
|
||||
fh, err := readFrameHeader(fr.headerBuf[:], fr.r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fh.Length > fr.maxReadSize {
|
||||
return nil, ErrFrameTooLarge
|
||||
}
|
||||
payload := fr.getReadBuf(fh.Length)
|
||||
if _, err := io.ReadFull(fr.r, payload); err != nil {
|
||||
if fh == invalidHTTP1LookingFrameHeader() {
|
||||
return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, payload)
|
||||
f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload)
|
||||
if err != nil {
|
||||
if ce, ok := err.(connError); ok {
|
||||
return nil, fr.connError(ce.Code, ce.Reason)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := fr.checkFrameOrder(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fr.lastFrame = f
|
||||
if fr.logReads {
|
||||
fr.debugReadLoggerf("http2: Framer %p: read %v", fr, summarizeFrame(f))
|
||||
}
|
||||
@@ -519,6 +550,24 @@ func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ReadFrame reads a single frame. The returned Frame is only valid
|
||||
// until the next call to ReadFrame or ReadFrameBodyForHeader.
|
||||
//
|
||||
// If the frame is larger than previously set with SetMaxReadFrameSize, the
|
||||
// returned error is ErrFrameTooLarge. Other errors may be of type
|
||||
// ConnectionError, StreamError, or anything else from the underlying
|
||||
// reader.
|
||||
//
|
||||
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
|
||||
// indicates the stream responsible for the error.
|
||||
func (fr *Framer) ReadFrame() (Frame, error) {
|
||||
fh, err := fr.ReadFrameHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fr.ReadFrameForHeader(fh)
|
||||
}
|
||||
|
||||
// connError returns ConnectionError(code) but first
|
||||
// stashes away a public reason to the caller can optionally relay it
|
||||
// to the peer before hanging up on them. This might help others debug
|
||||
@@ -531,20 +580,19 @@ func (fr *Framer) connError(code ErrCode, reason string) error {
|
||||
// checkFrameOrder reports an error if f is an invalid frame to return
|
||||
// next from ReadFrame. Mostly it checks whether HEADERS and
|
||||
// CONTINUATION frames are contiguous.
|
||||
func (fr *Framer) checkFrameOrder(f Frame) error {
|
||||
last := fr.lastFrame
|
||||
fr.lastFrame = f
|
||||
func (fr *Framer) checkFrameOrder(fh FrameHeader) error {
|
||||
lastType := fr.lastFrameType
|
||||
fr.lastFrameType = fh.Type
|
||||
if fr.AllowIllegalReads {
|
||||
return nil
|
||||
}
|
||||
|
||||
fh := f.Header()
|
||||
if fr.lastHeaderStream != 0 {
|
||||
if fh.Type != FrameContinuation {
|
||||
return fr.connError(ErrCodeProtocol,
|
||||
fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d",
|
||||
fh.Type, fh.StreamID,
|
||||
last.Header().Type, fr.lastHeaderStream))
|
||||
lastType, fr.lastHeaderStream))
|
||||
}
|
||||
if fh.StreamID != fr.lastHeaderStream {
|
||||
return fr.connError(ErrCodeProtocol,
|
||||
@@ -569,7 +617,7 @@ func (fr *Framer) checkFrameOrder(f Frame) error {
|
||||
|
||||
// A DataFrame conveys arbitrary, variable-length sequences of octets
|
||||
// associated with a stream.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.1
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.1
|
||||
type DataFrame struct {
|
||||
FrameHeader
|
||||
data []byte
|
||||
@@ -588,13 +636,14 @@ func (f *DataFrame) Data() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
func parseDataFrame(fc *frameCache, fh FrameHeader, payload []byte) (Frame, error) {
|
||||
func parseDataFrame(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) {
|
||||
if fh.StreamID == 0 {
|
||||
// DATA frames MUST be associated with a stream. If a
|
||||
// DATA frame is received whose stream identifier
|
||||
// field is 0x0, the recipient MUST respond with a
|
||||
// connection error (Section 5.4.1) of type
|
||||
// PROTOCOL_ERROR.
|
||||
countError("frame_data_stream_0")
|
||||
return nil, connError{ErrCodeProtocol, "DATA frame with stream ID 0"}
|
||||
}
|
||||
f := fc.getDataFrame()
|
||||
@@ -605,6 +654,7 @@ func parseDataFrame(fc *frameCache, fh FrameHeader, payload []byte) (Frame, erro
|
||||
var err error
|
||||
payload, padSize, err = readByte(payload)
|
||||
if err != nil {
|
||||
countError("frame_data_pad_byte_short")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -613,6 +663,7 @@ func parseDataFrame(fc *frameCache, fh FrameHeader, payload []byte) (Frame, erro
|
||||
// length of the frame payload, the recipient MUST
|
||||
// treat this as a connection error.
|
||||
// Filed: https://github.com/http2/http2-spec/issues/610
|
||||
countError("frame_data_pad_too_big")
|
||||
return nil, connError{ErrCodeProtocol, "pad size larger than data payload"}
|
||||
}
|
||||
f.data = payload[:len(payload)-int(padSize)]
|
||||
@@ -643,7 +694,7 @@ func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error {
|
||||
return f.WriteDataPadded(streamID, endStream, data, nil)
|
||||
}
|
||||
|
||||
// WriteData writes a DATA frame with optional padding.
|
||||
// WriteDataPadded writes a DATA frame with optional padding.
|
||||
//
|
||||
// If pad is nil, the padding bit is not sent.
|
||||
// The length of pad must not exceed 255 bytes.
|
||||
@@ -653,6 +704,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
|
||||
}
|
||||
@@ -682,20 +742,20 @@ 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
|
||||
// endpoints communicate, such as preferences and constraints on peer
|
||||
// behavior.
|
||||
//
|
||||
// See http://http2.github.io/http2-spec/#SETTINGS
|
||||
// See https://httpwg.org/specs/rfc7540.html#SETTINGS
|
||||
type SettingsFrame struct {
|
||||
FrameHeader
|
||||
p []byte
|
||||
}
|
||||
|
||||
func parseSettingsFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseSettingsFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
if fh.Flags.Has(FlagSettingsAck) && fh.Length > 0 {
|
||||
// When this (ACK 0x1) bit is set, the payload of the
|
||||
// SETTINGS frame MUST be empty. Receipt of a
|
||||
@@ -703,6 +763,7 @@ func parseSettingsFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error)
|
||||
// field value other than 0 MUST be treated as a
|
||||
// connection error (Section 5.4.1) of type
|
||||
// FRAME_SIZE_ERROR.
|
||||
countError("frame_settings_ack_with_length")
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
if fh.StreamID != 0 {
|
||||
@@ -713,14 +774,17 @@ func parseSettingsFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error)
|
||||
// field is anything other than 0x0, the endpoint MUST
|
||||
// respond with a connection error (Section 5.4.1) of
|
||||
// type PROTOCOL_ERROR.
|
||||
countError("frame_settings_has_stream")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
if len(p)%6 != 0 {
|
||||
countError("frame_settings_mod_6")
|
||||
// Expecting even number of 6 byte settings.
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
f := &SettingsFrame{FrameHeader: fh, p: p}
|
||||
if v, ok := f.Value(SettingInitialWindowSize); ok && v > (1<<31)-1 {
|
||||
countError("frame_settings_window_size_too_big")
|
||||
// Values above the maximum flow control window size of 2^31 - 1 MUST
|
||||
// be treated as a connection error (Section 5.4.1) of type
|
||||
// FLOW_CONTROL_ERROR.
|
||||
@@ -824,7 +888,7 @@ func (f *Framer) WriteSettingsAck() error {
|
||||
// A PingFrame is a mechanism for measuring a minimal round trip time
|
||||
// from the sender, as well as determining whether an idle connection
|
||||
// is still functional.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.7
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.7
|
||||
type PingFrame struct {
|
||||
FrameHeader
|
||||
Data [8]byte
|
||||
@@ -832,11 +896,13 @@ type PingFrame struct {
|
||||
|
||||
func (f *PingFrame) IsAck() bool { return f.Flags.Has(FlagPingAck) }
|
||||
|
||||
func parsePingFrame(_ *frameCache, fh FrameHeader, payload []byte) (Frame, error) {
|
||||
func parsePingFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) {
|
||||
if len(payload) != 8 {
|
||||
countError("frame_ping_length")
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
if fh.StreamID != 0 {
|
||||
countError("frame_ping_has_stream")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
f := &PingFrame{FrameHeader: fh}
|
||||
@@ -855,7 +921,7 @@ func (f *Framer) WritePing(ack bool, data [8]byte) error {
|
||||
}
|
||||
|
||||
// A GoAwayFrame informs the remote peer to stop creating streams on this connection.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.8
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.8
|
||||
type GoAwayFrame struct {
|
||||
FrameHeader
|
||||
LastStreamID uint32
|
||||
@@ -872,11 +938,13 @@ func (f *GoAwayFrame) DebugData() []byte {
|
||||
return f.debugData
|
||||
}
|
||||
|
||||
func parseGoAwayFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseGoAwayFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
if fh.StreamID != 0 {
|
||||
countError("frame_goaway_has_stream")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
if len(p) < 8 {
|
||||
countError("frame_goaway_short")
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
return &GoAwayFrame{
|
||||
@@ -912,19 +980,20 @@ func (f *UnknownFrame) Payload() []byte {
|
||||
return f.p
|
||||
}
|
||||
|
||||
func parseUnknownFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseUnknownFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
return &UnknownFrame{fh, p}, nil
|
||||
}
|
||||
|
||||
// A WindowUpdateFrame is used to implement flow control.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.9
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.9
|
||||
type WindowUpdateFrame struct {
|
||||
FrameHeader
|
||||
Increment uint32 // never read with high bit set
|
||||
}
|
||||
|
||||
func parseWindowUpdateFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseWindowUpdateFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
if len(p) != 4 {
|
||||
countError("frame_windowupdate_bad_len")
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
inc := binary.BigEndian.Uint32(p[:4]) & 0x7fffffff // mask off high reserved bit
|
||||
@@ -936,8 +1005,10 @@ func parseWindowUpdateFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, err
|
||||
// control window MUST be treated as a connection
|
||||
// error (Section 5.4.1).
|
||||
if fh.StreamID == 0 {
|
||||
countError("frame_windowupdate_zero_inc_conn")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
countError("frame_windowupdate_zero_inc_stream")
|
||||
return nil, streamError(fh.StreamID, ErrCodeProtocol)
|
||||
}
|
||||
return &WindowUpdateFrame{
|
||||
@@ -988,7 +1059,7 @@ func (f *HeadersFrame) HasPriority() bool {
|
||||
return f.FrameHeader.Flags.Has(FlagHeadersPriority)
|
||||
}
|
||||
|
||||
func parseHeadersFrame(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err error) {
|
||||
func parseHeadersFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (_ Frame, err error) {
|
||||
hf := &HeadersFrame{
|
||||
FrameHeader: fh,
|
||||
}
|
||||
@@ -997,11 +1068,13 @@ func parseHeadersFrame(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err er
|
||||
// is received whose stream identifier field is 0x0, the recipient MUST
|
||||
// respond with a connection error (Section 5.4.1) of type
|
||||
// PROTOCOL_ERROR.
|
||||
countError("frame_headers_zero_stream")
|
||||
return nil, connError{ErrCodeProtocol, "HEADERS frame with stream ID 0"}
|
||||
}
|
||||
var padLength uint8
|
||||
if fh.Flags.Has(FlagHeadersPadded) {
|
||||
if p, padLength, err = readByte(p); err != nil {
|
||||
countError("frame_headers_pad_short")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1009,16 +1082,19 @@ func parseHeadersFrame(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err er
|
||||
var v uint32
|
||||
p, v, err = readUint32(p)
|
||||
if err != nil {
|
||||
countError("frame_headers_prio_short")
|
||||
return nil, err
|
||||
}
|
||||
hf.Priority.StreamDep = v & 0x7fffffff
|
||||
hf.Priority.Exclusive = (v != hf.Priority.StreamDep) // high bit was set
|
||||
p, hf.Priority.Weight, err = readByte(p)
|
||||
if err != nil {
|
||||
countError("frame_headers_prio_weight_short")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(p)-int(padLength) <= 0 {
|
||||
if len(p)-int(padLength) < 0 {
|
||||
countError("frame_headers_pad_too_big")
|
||||
return nil, streamError(fh.StreamID, ErrCodeProtocol)
|
||||
}
|
||||
hf.headerFragBuf = p[:len(p)-int(padLength)]
|
||||
@@ -1098,13 +1174,22 @@ func (f *Framer) WriteHeaders(p HeadersFrameParam) error {
|
||||
}
|
||||
|
||||
// A PriorityFrame specifies the sender-advised priority of a stream.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.3
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.3
|
||||
type PriorityFrame struct {
|
||||
FrameHeader
|
||||
PriorityParam
|
||||
}
|
||||
|
||||
// PriorityParam are the stream prioritzation parameters.
|
||||
var defaultRFC9218Priority = PriorityParam{
|
||||
incremental: 0,
|
||||
urgency: 3,
|
||||
}
|
||||
|
||||
// Note that HTTP/2 has had two different prioritization schemes, and
|
||||
// PriorityParam struct below is a superset of both schemes. The exported
|
||||
// symbols are from RFC 7540 and the non-exported ones are from RFC 9218.
|
||||
|
||||
// PriorityParam are the stream prioritization parameters.
|
||||
type PriorityParam struct {
|
||||
// StreamDep is a 31-bit stream identifier for the
|
||||
// stream that this stream depends on. Zero means no
|
||||
@@ -1119,17 +1204,33 @@ type PriorityParam struct {
|
||||
// the spec, "Add one to the value to obtain a weight between
|
||||
// 1 and 256."
|
||||
Weight uint8
|
||||
|
||||
// "The urgency (u) parameter value is Integer (see Section 3.3.1 of
|
||||
// [STRUCTURED-FIELDS]), between 0 and 7 inclusive, in descending order of
|
||||
// priority. The default is 3."
|
||||
urgency uint8
|
||||
|
||||
// "The incremental (i) parameter value is Boolean (see Section 3.3.6 of
|
||||
// [STRUCTURED-FIELDS]). It indicates if an HTTP response can be processed
|
||||
// incrementally, i.e., provide some meaningful output as chunks of the
|
||||
// response arrive."
|
||||
//
|
||||
// We use uint8 (i.e. 0 is false, 1 is true) instead of bool so we can
|
||||
// avoid unnecessary type conversions and because either type takes 1 byte.
|
||||
incremental uint8
|
||||
}
|
||||
|
||||
func (p PriorityParam) IsZero() bool {
|
||||
return p == PriorityParam{}
|
||||
}
|
||||
|
||||
func parsePriorityFrame(_ *frameCache, fh FrameHeader, payload []byte) (Frame, error) {
|
||||
func parsePriorityFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) {
|
||||
if fh.StreamID == 0 {
|
||||
countError("frame_priority_zero_stream")
|
||||
return nil, connError{ErrCodeProtocol, "PRIORITY frame with stream ID 0"}
|
||||
}
|
||||
if len(payload) != 5 {
|
||||
countError("frame_priority_bad_length")
|
||||
return nil, connError{ErrCodeFrameSize, fmt.Sprintf("PRIORITY frame payload size was %d; want 5", len(payload))}
|
||||
}
|
||||
v := binary.BigEndian.Uint32(payload[:4])
|
||||
@@ -1166,17 +1267,19 @@ func (f *Framer) WritePriority(streamID uint32, p PriorityParam) error {
|
||||
}
|
||||
|
||||
// A RSTStreamFrame allows for abnormal termination of a stream.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.4
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4
|
||||
type RSTStreamFrame struct {
|
||||
FrameHeader
|
||||
ErrCode ErrCode
|
||||
}
|
||||
|
||||
func parseRSTStreamFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseRSTStreamFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
if len(p) != 4 {
|
||||
countError("frame_rststream_bad_len")
|
||||
return nil, ConnectionError(ErrCodeFrameSize)
|
||||
}
|
||||
if fh.StreamID == 0 {
|
||||
countError("frame_rststream_zero_stream")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
return &RSTStreamFrame{fh, ErrCode(binary.BigEndian.Uint32(p[:4]))}, nil
|
||||
@@ -1196,14 +1299,15 @@ func (f *Framer) WriteRSTStream(streamID uint32, code ErrCode) error {
|
||||
}
|
||||
|
||||
// A ContinuationFrame is used to continue a sequence of header block fragments.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.10
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.10
|
||||
type ContinuationFrame struct {
|
||||
FrameHeader
|
||||
headerFragBuf []byte
|
||||
}
|
||||
|
||||
func parseContinuationFrame(_ *frameCache, fh FrameHeader, p []byte) (Frame, error) {
|
||||
func parseContinuationFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) {
|
||||
if fh.StreamID == 0 {
|
||||
countError("frame_continuation_zero_stream")
|
||||
return nil, connError{ErrCodeProtocol, "CONTINUATION frame with stream ID 0"}
|
||||
}
|
||||
return &ContinuationFrame{fh, p}, nil
|
||||
@@ -1236,7 +1340,7 @@ func (f *Framer) WriteContinuation(streamID uint32, endHeaders bool, headerBlock
|
||||
}
|
||||
|
||||
// A PushPromiseFrame is used to initiate a server stream.
|
||||
// See http://http2.github.io/http2-spec/#rfc.section.6.6
|
||||
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.6
|
||||
type PushPromiseFrame struct {
|
||||
FrameHeader
|
||||
PromiseID uint32
|
||||
@@ -1252,7 +1356,7 @@ func (f *PushPromiseFrame) HeadersEnded() bool {
|
||||
return f.FrameHeader.Flags.Has(FlagPushPromiseEndHeaders)
|
||||
}
|
||||
|
||||
func parsePushPromise(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err error) {
|
||||
func parsePushPromise(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (_ Frame, err error) {
|
||||
pp := &PushPromiseFrame{
|
||||
FrameHeader: fh,
|
||||
}
|
||||
@@ -1263,6 +1367,7 @@ func parsePushPromise(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err err
|
||||
// with. If the stream identifier field specifies the value
|
||||
// 0x0, a recipient MUST respond with a connection error
|
||||
// (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
countError("frame_pushpromise_zero_stream")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
// The PUSH_PROMISE frame includes optional padding.
|
||||
@@ -1270,18 +1375,21 @@ func parsePushPromise(_ *frameCache, fh FrameHeader, p []byte) (_ Frame, err err
|
||||
var padLength uint8
|
||||
if fh.Flags.Has(FlagPushPromisePadded) {
|
||||
if p, padLength, err = readByte(p); err != nil {
|
||||
countError("frame_pushpromise_pad_short")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p, pp.PromiseID, err = readUint32(p)
|
||||
if err != nil {
|
||||
countError("frame_pushpromise_promiseid_short")
|
||||
return
|
||||
}
|
||||
pp.PromiseID = pp.PromiseID & (1<<31 - 1)
|
||||
|
||||
if int(padLength) > len(p) {
|
||||
// like the DATA frame, error out if padding is longer than the body.
|
||||
countError("frame_pushpromise_pad_too_big")
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
pp.headerFragBuf = p[:len(p)-int(padLength)]
|
||||
@@ -1444,7 +1552,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
|
||||
pf := mh.PseudoFields()
|
||||
for i, hf := range pf {
|
||||
switch hf.Name {
|
||||
case ":method", ":path", ":scheme", ":authority":
|
||||
case ":method", ":path", ":scheme", ":authority", ":protocol":
|
||||
isRequest = true
|
||||
case ":status":
|
||||
isResponse = true
|
||||
@@ -1452,7 +1560,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
|
||||
return pseudoHeaderError(hf.Name)
|
||||
}
|
||||
// Check for duplicates.
|
||||
// This would be a bad algorithm, but N is 4.
|
||||
// This would be a bad algorithm, but N is 5.
|
||||
// And this doesn't allocate.
|
||||
for _, hf2 := range pf[:i] {
|
||||
if hf.Name == hf2.Name {
|
||||
@@ -1467,19 +1575,18 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
|
||||
}
|
||||
|
||||
func (fr *Framer) maxHeaderStringLen() int {
|
||||
v := fr.maxHeaderListSize()
|
||||
if uint32(int(v)) == v {
|
||||
return int(v)
|
||||
v := int(fr.maxHeaderListSize())
|
||||
if v < 0 {
|
||||
// If maxHeaderListSize overflows an int, use no limit (0).
|
||||
return 0
|
||||
}
|
||||
// They had a crazy big number for MaxHeaderBytes anyway,
|
||||
// so give them unlimited header lengths:
|
||||
return 0
|
||||
return v
|
||||
}
|
||||
|
||||
// readMetaFrame returns 0 or more CONTINUATION frames from fr and
|
||||
// merge them into into the provided hf and returns a MetaHeadersFrame
|
||||
// merge them into the provided hf and returns a MetaHeadersFrame
|
||||
// with the decoded hpack values.
|
||||
func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
||||
func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) {
|
||||
if fr.AllowIllegalReads {
|
||||
return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders")
|
||||
}
|
||||
@@ -1498,7 +1605,8 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
||||
fr.debugReadLoggerf("http2: decoded hpack field %+v", hf)
|
||||
}
|
||||
if !httpguts.ValidHeaderFieldValue(hf.Value) {
|
||||
invalid = headerFieldValueError(hf.Value)
|
||||
// Don't include the value in the error, because it may be sensitive.
|
||||
invalid = headerFieldValueError(hf.Name)
|
||||
}
|
||||
isPseudo := strings.HasPrefix(hf.Name, ":")
|
||||
if isPseudo {
|
||||
@@ -1521,6 +1629,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
||||
if size > remainSize {
|
||||
hdec.SetEmitEnabled(false)
|
||||
mh.Truncated = true
|
||||
remainSize = 0
|
||||
return
|
||||
}
|
||||
remainSize -= size
|
||||
@@ -1533,8 +1642,38 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
||||
var hc headersOrContinuation = hf
|
||||
for {
|
||||
frag := hc.HeaderBlockFragment()
|
||||
|
||||
// Avoid parsing large amounts of headers that we will then discard.
|
||||
// If the sender exceeds the max header list size by too much,
|
||||
// skip parsing the fragment and close the connection.
|
||||
//
|
||||
// "Too much" is either any CONTINUATION frame after we've already
|
||||
// exceeded the max header list size (in which case remainSize is 0),
|
||||
// or a frame whose encoded size is more than twice the remaining
|
||||
// header list bytes we're willing to accept.
|
||||
if int64(len(frag)) > int64(2*remainSize) {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: header list too large")
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return mh, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
// Also close the connection after any CONTINUATION frame following an
|
||||
// invalid header, since we stop tracking the size of the headers after
|
||||
// an invalid one.
|
||||
if invalid != nil {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: invalid header: %v", invalid)
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return mh, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
if _, err := hdec.Write(frag); err != nil {
|
||||
return nil, ConnectionError(ErrCodeCompression)
|
||||
return mh, ConnectionError(ErrCodeCompression)
|
||||
}
|
||||
|
||||
if hc.HeadersEnded() {
|
||||
@@ -1551,7 +1690,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
||||
mh.HeadersFrame.invalidate()
|
||||
|
||||
if err := hdec.Close(); err != nil {
|
||||
return nil, ConnectionError(ErrCodeCompression)
|
||||
return mh, ConnectionError(ErrCodeCompression)
|
||||
}
|
||||
if invalid != nil {
|
||||
fr.errDetail = invalid
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
// +build go1.11
|
||||
|
||||
package http2
|
||||
|
||||
import "net/textproto"
|
||||
|
||||
func traceHasWroteHeaderField(trace *clientTrace) bool {
|
||||
return trace != nil && trace.WroteHeaderField != nil
|
||||
}
|
||||
|
||||
func traceWroteHeaderField(trace *clientTrace, k, v string) {
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField(k, []string{v})
|
||||
}
|
||||
}
|
||||
|
||||
func traceGot1xxResponseFunc(trace *clientTrace) func(int, textproto.MIMEHeader) error {
|
||||
if trace != nil {
|
||||
return trace.Got1xxResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func transportExpectContinueTimeout(t1 *http.Transport) time.Duration {
|
||||
return t1.ExpectContinueTimeout
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"time"
|
||||
)
|
||||
|
||||
type contextContext interface {
|
||||
context.Context
|
||||
}
|
||||
|
||||
var errCanceled = context.Canceled
|
||||
|
||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx contextContext, cancel func()) {
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
||||
if hs := opts.baseConfig(); hs != nil {
|
||||
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func contextWithCancel(ctx contextContext) (_ contextContext, cancel func()) {
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
func requestWithContext(req *http.Request, ctx contextContext) *http.Request {
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
type clientTrace httptrace.ClientTrace
|
||||
|
||||
func reqContext(r *http.Request) context.Context { return r.Context() }
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration {
|
||||
if t.t1 != nil {
|
||||
return t.t1.IdleConnTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setResponseUncompressed(res *http.Response) { res.Uncompressed = true }
|
||||
|
||||
func traceGetConn(req *http.Request, hostPort string) {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
if trace == nil || trace.GetConn == nil {
|
||||
return
|
||||
}
|
||||
trace.GetConn(hostPort)
|
||||
}
|
||||
|
||||
func traceGotConn(req *http.Request, cc *ClientConn) {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
if trace == nil || trace.GotConn == nil {
|
||||
return
|
||||
}
|
||||
ci := httptrace.GotConnInfo{Conn: cc.tconn}
|
||||
cc.mu.Lock()
|
||||
ci.Reused = cc.nextStreamID > 1
|
||||
ci.WasIdle = len(cc.streams) == 0 && ci.Reused
|
||||
if ci.WasIdle && !cc.lastActive.IsZero() {
|
||||
ci.IdleTime = time.Now().Sub(cc.lastActive)
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
trace.GotConn(ci)
|
||||
}
|
||||
|
||||
func traceWroteHeaders(trace *clientTrace) {
|
||||
if trace != nil && trace.WroteHeaders != nil {
|
||||
trace.WroteHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
func traceGot100Continue(trace *clientTrace) {
|
||||
if trace != nil && trace.Got100Continue != nil {
|
||||
trace.Got100Continue()
|
||||
}
|
||||
}
|
||||
|
||||
func traceWait100Continue(trace *clientTrace) {
|
||||
if trace != nil && trace.Wait100Continue != nil {
|
||||
trace.Wait100Continue()
|
||||
}
|
||||
}
|
||||
|
||||
func traceWroteRequest(trace *clientTrace, err error) {
|
||||
if trace != nil && trace.WroteRequest != nil {
|
||||
trace.WroteRequest(httptrace.WroteRequestInfo{Err: err})
|
||||
}
|
||||
}
|
||||
|
||||
func traceFirstResponseByte(trace *clientTrace) {
|
||||
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||
trace.GotFirstResponseByte()
|
||||
}
|
||||
}
|
||||
|
||||
func requestTrace(req *http.Request) *clientTrace {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
return (*clientTrace)(trace)
|
||||
}
|
||||
|
||||
// Ping sends a PING frame to the server and waits for the ack.
|
||||
func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||
return cc.ping(ctx)
|
||||
}
|
||||
|
||||
// Shutdown gracefully closes the client connection, waiting for running streams to complete.
|
||||
func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
||||
return cc.shutdown(ctx)
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7,!go1.8
|
||||
|
||||
package http2
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// temporary copy of Go 1.7's private tls.Config.clone:
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: c.Rand,
|
||||
Time: c.Time,
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: c.MinVersion,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||
Renegotiation: c.Renegotiation,
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
c2 := c.Clone()
|
||||
c2.GetClientCertificate = c.GetClientCertificate // golang.org/issue/19264
|
||||
return c2
|
||||
}
|
||||
|
||||
var _ http.Pusher = (*responseWriter)(nil)
|
||||
|
||||
// Push implements http.Pusher.
|
||||
func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
internalOpts := pushOptions{}
|
||||
if opts != nil {
|
||||
internalOpts.Method = opts.Method
|
||||
internalOpts.Header = opts.Header
|
||||
}
|
||||
return w.push(target, internalOpts)
|
||||
}
|
||||
|
||||
func configureServer18(h1 *http.Server, h2 *Server) error {
|
||||
if h2.IdleTimeout == 0 {
|
||||
if h1.IdleTimeout != 0 {
|
||||
h2.IdleTimeout = h1.IdleTimeout
|
||||
} else {
|
||||
h2.IdleTimeout = h1.ReadTimeout
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldLogPanic(panicValue interface{}) bool {
|
||||
return panicValue != nil && panicValue != http.ErrAbortHandler
|
||||
}
|
||||
|
||||
func reqGetBody(req *http.Request) func() (io.ReadCloser, error) {
|
||||
return req.GetBody
|
||||
}
|
||||
|
||||
func reqBodyIsNoBody(body io.ReadCloser) bool {
|
||||
return body == http.NoBody
|
||||
}
|
||||
|
||||
func go18httpNoBody() io.ReadCloser { return http.NoBody } // for tests only
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func configureServer19(s *http.Server, conf *Server) error {
|
||||
s.RegisterOnShutdown(conf.state.startGracefulShutdown)
|
||||
return nil
|
||||
}
|
||||
+14
-3
@@ -15,21 +15,32 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
|
||||
|
||||
// Setting DebugGoroutines to false during a test to disable goroutine debugging
|
||||
// results in race detector complaints when a test leaves goroutines running before
|
||||
// returning. Tests shouldn't do this, of course, but when they do it generally shows
|
||||
// up as infrequent, hard-to-debug flakes. (See #66519.)
|
||||
//
|
||||
// Disable goroutine debugging during individual tests with an atomic bool.
|
||||
// (Note that it's safe to enable/disable debugging mid-test, so the actual race condition
|
||||
// here is harmless.)
|
||||
var disableDebugGoroutines atomic.Bool
|
||||
|
||||
type goroutineLock uint64
|
||||
|
||||
func newGoroutineLock() goroutineLock {
|
||||
if !DebugGoroutines {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return 0
|
||||
}
|
||||
return goroutineLock(curGoroutineID())
|
||||
}
|
||||
|
||||
func (g goroutineLock) check() {
|
||||
if !DebugGoroutines {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return
|
||||
}
|
||||
if curGoroutineID() != uint64(g) {
|
||||
@@ -38,7 +49,7 @@ func (g goroutineLock) check() {
|
||||
}
|
||||
|
||||
func (g goroutineLock) checkNotOn() {
|
||||
if !DebugGoroutines {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return
|
||||
}
|
||||
if curGoroutineID() == uint64(g) {
|
||||
|
||||
+7
-2
@@ -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
|
||||
@@ -150,7 +155,7 @@ func appendIndexed(dst []byte, i uint64) []byte {
|
||||
// extended buffer.
|
||||
//
|
||||
// If f.Sensitive is true, "Never Indexed" representation is used. If
|
||||
// f.Sensitive is false and indexing is true, "Inremental Indexing"
|
||||
// f.Sensitive is false and indexing is true, "Incremental Indexing"
|
||||
// representation is used.
|
||||
func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
|
||||
dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
|
||||
@@ -191,7 +196,7 @@ func appendTableSize(dst []byte, v uint32) []byte {
|
||||
// bit prefix, to dst and returns the extended buffer.
|
||||
//
|
||||
// See
|
||||
// http://http2.github.io/http2-spec/compression.html#integer.representation
|
||||
// https://httpwg.org/specs/rfc7541.html#integer.representation
|
||||
func appendVarInt(dst []byte, n byte, i uint64) []byte {
|
||||
k := uint64((1 << n) - 1)
|
||||
if i < k {
|
||||
|
||||
+67
-40
@@ -59,7 +59,7 @@ func (hf HeaderField) String() string {
|
||||
|
||||
// Size returns the size of an entry per RFC 7541 section 4.1.
|
||||
func (hf HeaderField) Size() uint32 {
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.4.1
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.4.1
|
||||
// "The size of the dynamic table is the sum of the size of
|
||||
// its entries. The size of an entry is the sum of its name's
|
||||
// length in octets (as defined in Section 5.2), its value's
|
||||
@@ -92,6 +92,8 @@ type Decoder struct {
|
||||
// saveBuf is previous data passed to Write which we weren't able
|
||||
// to fully parse before. Unlike buf, we own this data.
|
||||
saveBuf bytes.Buffer
|
||||
|
||||
firstField bool // processing the first field of the header block
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder with the provided maximum dynamic
|
||||
@@ -101,6 +103,7 @@ func NewDecoder(maxDynamicTableSize uint32, emitFunc func(f HeaderField)) *Decod
|
||||
d := &Decoder{
|
||||
emit: emitFunc,
|
||||
emitEnabled: true,
|
||||
firstField: true,
|
||||
}
|
||||
d.dynTab.table.init()
|
||||
d.dynTab.allowedMaxSize = maxDynamicTableSize
|
||||
@@ -155,7 +158,7 @@ func (d *Decoder) SetAllowedMaxDynamicTableSize(v uint32) {
|
||||
}
|
||||
|
||||
type dynamicTable struct {
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.2.3.2
|
||||
table headerFieldTable
|
||||
size uint32 // in bytes
|
||||
maxSize uint32 // current maxSize
|
||||
@@ -208,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.
|
||||
@@ -226,11 +229,15 @@ func (d *Decoder) DecodeFull(p []byte) ([]HeaderField, error) {
|
||||
return hf, nil
|
||||
}
|
||||
|
||||
// Close declares that the decoding is complete and resets the Decoder
|
||||
// to be reused again for a new header block. If there is any remaining
|
||||
// data in the decoder's buffer, Close returns an error.
|
||||
func (d *Decoder) Close() error {
|
||||
if d.saveBuf.Len() > 0 {
|
||||
d.saveBuf.Reset()
|
||||
return DecodingError{errors.New("truncated headers")}
|
||||
}
|
||||
d.firstField = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -266,6 +273,7 @@ func (d *Decoder) Write(p []byte) (n int, err error) {
|
||||
d.saveBuf.Write(d.buf)
|
||||
return len(p), nil
|
||||
}
|
||||
d.firstField = false
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -299,27 +307,27 @@ func (d *Decoder) parseHeaderFieldRepr() error {
|
||||
case b&128 != 0:
|
||||
// Indexed representation.
|
||||
// High bit set?
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.1
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.6.1
|
||||
return d.parseFieldIndexed()
|
||||
case b&192 == 64:
|
||||
// 6.2.1 Literal Header Field with Incremental Indexing
|
||||
// 0b10xxxxxx: top two bits are 10
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.1
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1
|
||||
return d.parseFieldLiteral(6, indexedTrue)
|
||||
case b&240 == 0:
|
||||
// 6.2.2 Literal Header Field without Indexing
|
||||
// 0b0000xxxx: top four bits are 0000
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.2
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2
|
||||
return d.parseFieldLiteral(4, indexedFalse)
|
||||
case b&240 == 16:
|
||||
// 6.2.3 Literal Header Field never Indexed
|
||||
// 0b0001xxxx: top four bits are 0001
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.3
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3
|
||||
return d.parseFieldLiteral(4, indexedNever)
|
||||
case b&224 == 32:
|
||||
// 6.3 Dynamic Table Size Update
|
||||
// Top three bits are '001'.
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.6.3
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.6.3
|
||||
return d.parseDynamicTableSizeUpdate()
|
||||
}
|
||||
|
||||
@@ -351,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 {
|
||||
@@ -358,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)
|
||||
@@ -391,7 +412,7 @@ func (d *Decoder) callEmit(hf HeaderField) error {
|
||||
func (d *Decoder) parseDynamicTableSizeUpdate() error {
|
||||
// RFC 7541, sec 4.2: This dynamic table size update MUST occur at the
|
||||
// beginning of the first header block following the change to the dynamic table size.
|
||||
if d.dynTab.size > 0 {
|
||||
if !d.firstField && d.dynTab.size > 0 {
|
||||
return DecodingError{errors.New("dynamic table size update MUST occur at the beginning of a header block")}
|
||||
}
|
||||
|
||||
@@ -412,7 +433,7 @@ var errVarintOverflow = DecodingError{errors.New("varint integer overflow")}
|
||||
|
||||
// readVarInt reads an unsigned variable length integer off the
|
||||
// beginning of p. n is the parameter as described in
|
||||
// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1.
|
||||
// https://httpwg.org/specs/rfc7541.html#rfc.section.5.1.
|
||||
//
|
||||
// n must always be between 1 and 8.
|
||||
//
|
||||
@@ -451,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
|
||||
return u, p, errNeedMore
|
||||
}
|
||||
if !isHuff {
|
||||
if wantStr {
|
||||
s = string(p[:strLen])
|
||||
}
|
||||
return s, p[strLen:], nil
|
||||
u.isHuff = isHuff
|
||||
u.b = p[:strLen]
|
||||
return u, p[strLen:], nil
|
||||
}
|
||||
|
||||
type undecodedString struct {
|
||||
isHuff bool
|
||||
b []byte
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeString(u undecodedString) (string, error) {
|
||||
if !u.isHuff {
|
||||
return string(u.b), 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+68
-64
@@ -105,7 +105,14 @@ func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// incomparable is a zero-width, non-comparable type. Adding it to a struct
|
||||
// makes that struct also non-comparable, and generally doesn't add
|
||||
// any size (as long as it's first).
|
||||
type incomparable [0]func()
|
||||
|
||||
type node struct {
|
||||
_ incomparable
|
||||
|
||||
// children is non-nil for internal nodes
|
||||
children *[256]*node
|
||||
|
||||
@@ -133,50 +140,79 @@ func buildRootHuffmanNode() {
|
||||
panic("unexpected size")
|
||||
}
|
||||
lazyRootHuffmanNode = newInternalNode()
|
||||
for i, code := range huffmanCodes {
|
||||
addDecoderNode(byte(i), code, huffmanCodeLen[i])
|
||||
}
|
||||
}
|
||||
// allocate a leaf node for each of the 256 symbols
|
||||
leaves := new([256]node)
|
||||
|
||||
func addDecoderNode(sym byte, code uint32, codeLen uint8) {
|
||||
cur := lazyRootHuffmanNode
|
||||
for codeLen > 8 {
|
||||
codeLen -= 8
|
||||
i := uint8(code >> codeLen)
|
||||
if cur.children[i] == nil {
|
||||
cur.children[i] = newInternalNode()
|
||||
for sym, code := range huffmanCodes {
|
||||
codeLen := huffmanCodeLen[sym]
|
||||
|
||||
cur := lazyRootHuffmanNode
|
||||
for codeLen > 8 {
|
||||
codeLen -= 8
|
||||
i := uint8(code >> codeLen)
|
||||
if cur.children[i] == nil {
|
||||
cur.children[i] = newInternalNode()
|
||||
}
|
||||
cur = cur.children[i]
|
||||
}
|
||||
shift := 8 - codeLen
|
||||
start, end := int(uint8(code<<shift)), int(1<<shift)
|
||||
|
||||
leaves[sym].sym = byte(sym)
|
||||
leaves[sym].codeLen = codeLen
|
||||
for i := start; i < start+end; i++ {
|
||||
cur.children[i] = &leaves[sym]
|
||||
}
|
||||
cur = cur.children[i]
|
||||
}
|
||||
shift := 8 - codeLen
|
||||
start, end := int(uint8(code<<shift)), int(1<<shift)
|
||||
for i := start; i < start+end; i++ {
|
||||
cur.children[i] = &node{sym: sym, codeLen: codeLen}
|
||||
}
|
||||
}
|
||||
|
||||
// AppendHuffmanString appends s, as encoded in Huffman codes, to dst
|
||||
// and returns the extended buffer.
|
||||
func AppendHuffmanString(dst []byte, s string) []byte {
|
||||
rembits := uint8(8)
|
||||
|
||||
// This relies on the maximum huffman code length being 30 (See tables.go huffmanCodeLen array)
|
||||
// So if a uint64 buffer has less than 32 valid bits can always accommodate another huffmanCode.
|
||||
var (
|
||||
x uint64 // buffer
|
||||
n uint // number valid of bits present in x
|
||||
)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if rembits == 8 {
|
||||
dst = append(dst, 0)
|
||||
c := s[i]
|
||||
n += uint(huffmanCodeLen[c])
|
||||
x <<= huffmanCodeLen[c] % 64
|
||||
x |= uint64(huffmanCodes[c])
|
||||
if n >= 32 {
|
||||
n %= 32 // Normally would be -= 32 but %= 32 informs compiler 0 <= n <= 31 for upcoming shift
|
||||
y := uint32(x >> n) // Compiler doesn't combine memory writes if y isn't uint32
|
||||
dst = append(dst, byte(y>>24), byte(y>>16), byte(y>>8), byte(y))
|
||||
}
|
||||
dst, rembits = appendByteToHuffmanCode(dst, rembits, s[i])
|
||||
}
|
||||
|
||||
if rembits < 8 {
|
||||
// special EOS symbol
|
||||
code := uint32(0x3fffffff)
|
||||
nbits := uint8(30)
|
||||
|
||||
t := uint8(code >> (nbits - rembits))
|
||||
dst[len(dst)-1] |= t
|
||||
// Add padding bits if necessary
|
||||
if over := n % 8; over > 0 {
|
||||
const (
|
||||
eosCode = 0x3fffffff
|
||||
eosNBits = 30
|
||||
eosPadByte = eosCode >> (eosNBits - 8)
|
||||
)
|
||||
pad := 8 - over
|
||||
x = (x << pad) | (eosPadByte >> over)
|
||||
n += pad // 8 now divides into n exactly
|
||||
}
|
||||
|
||||
return dst
|
||||
// n in (0, 8, 16, 24, 32)
|
||||
switch n / 8 {
|
||||
case 0:
|
||||
return dst
|
||||
case 1:
|
||||
return append(dst, byte(x))
|
||||
case 2:
|
||||
y := uint16(x)
|
||||
return append(dst, byte(y>>8), byte(y))
|
||||
case 3:
|
||||
y := uint16(x >> 8)
|
||||
return append(dst, byte(y>>8), byte(y), byte(x))
|
||||
}
|
||||
// case 4:
|
||||
y := uint32(x)
|
||||
return append(dst, byte(y>>24), byte(y>>16), byte(y>>8), byte(y))
|
||||
}
|
||||
|
||||
// HuffmanEncodeLength returns the number of bytes required to encode
|
||||
@@ -188,35 +224,3 @@ func HuffmanEncodeLength(s string) uint64 {
|
||||
}
|
||||
return (n + 7) / 8
|
||||
}
|
||||
|
||||
// appendByteToHuffmanCode appends Huffman code for c to dst and
|
||||
// returns the extended buffer and the remaining bits in the last
|
||||
// element. The appending is not byte aligned and the remaining bits
|
||||
// in the last element of dst is given in rembits.
|
||||
func appendByteToHuffmanCode(dst []byte, rembits uint8, c byte) ([]byte, uint8) {
|
||||
code := huffmanCodes[c]
|
||||
nbits := huffmanCodeLen[c]
|
||||
|
||||
for {
|
||||
if rembits > nbits {
|
||||
t := uint8(code << (rembits - nbits))
|
||||
dst[len(dst)-1] |= t
|
||||
rembits -= nbits
|
||||
break
|
||||
}
|
||||
|
||||
t := uint8(code >> (nbits - rembits))
|
||||
dst[len(dst)-1] |= t
|
||||
|
||||
nbits -= rembits
|
||||
rembits = 8
|
||||
|
||||
if nbits == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
dst = append(dst, 0)
|
||||
}
|
||||
|
||||
return dst, rembits
|
||||
}
|
||||
|
||||
+188
@@ -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},
|
||||
},
|
||||
}
|
||||
+1
-77
@@ -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,
|
||||
|
||||
+82
-57
@@ -11,9 +11,6 @@
|
||||
// requires Go 1.6 or later)
|
||||
//
|
||||
// See https://http2.github.io/ for more information on HTTP/2.
|
||||
//
|
||||
// See https://http2.golang.org/ for a test server running this code.
|
||||
//
|
||||
package http2 // import "golang.org/x/net/http2"
|
||||
|
||||
import (
|
||||
@@ -21,13 +18,14 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
@@ -36,7 +34,15 @@ var (
|
||||
VerboseLogs bool
|
||||
logFrameWrites bool
|
||||
logFrameReads bool
|
||||
inTests bool
|
||||
|
||||
// Enabling extended CONNECT by causes browsers to attempt to use
|
||||
// WebSockets-over-HTTP/2. This results in problems when the server's websocket
|
||||
// package doesn't support extended CONNECT.
|
||||
//
|
||||
// Disable extended CONNECT by default for now.
|
||||
//
|
||||
// Issue #71128.
|
||||
disableExtendedConnectProtocol = true
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -49,6 +55,9 @@ func init() {
|
||||
logFrameWrites = true
|
||||
logFrameReads = true
|
||||
}
|
||||
if strings.Contains(e, "http2xconnect=1") {
|
||||
disableExtendedConnectProtocol = false
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -57,14 +66,14 @@ const (
|
||||
ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
||||
|
||||
// SETTINGS_MAX_FRAME_SIZE default
|
||||
// http://http2.github.io/http2-spec/#rfc.section.6.5.2
|
||||
// https://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2
|
||||
initialMaxFrameSize = 16384
|
||||
|
||||
// NextProtoTLS is the NPN/ALPN protocol negotiated during
|
||||
// HTTP/2's TLS setup.
|
||||
NextProtoTLS = "h2"
|
||||
|
||||
// http://http2.github.io/http2-spec/#SettingValues
|
||||
// https://httpwg.org/specs/rfc7540.html#SettingValues
|
||||
initialHeaderTableSize = 4096
|
||||
|
||||
initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size
|
||||
@@ -113,7 +122,7 @@ func (st streamState) String() string {
|
||||
// Setting is a setting parameter: which setting it is, and its value.
|
||||
type Setting struct {
|
||||
// ID is which setting is being set.
|
||||
// See http://http2.github.io/http2-spec/#SettingValues
|
||||
// See https://httpwg.org/specs/rfc7540.html#SettingFormat
|
||||
ID SettingID
|
||||
|
||||
// Val is the value.
|
||||
@@ -140,30 +149,36 @@ func (s Setting) Valid() error {
|
||||
if s.Val < 16384 || s.Val > 1<<24-1 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
case SettingEnableConnectProtocol:
|
||||
if s.Val != 1 && s.Val != 0 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A SettingID is an HTTP/2 setting as defined in
|
||||
// http://http2.github.io/http2-spec/#iana-settings
|
||||
// https://httpwg.org/specs/rfc7540.html#iana-settings
|
||||
type SettingID uint16
|
||||
|
||||
const (
|
||||
SettingHeaderTableSize SettingID = 0x1
|
||||
SettingEnablePush SettingID = 0x2
|
||||
SettingMaxConcurrentStreams SettingID = 0x3
|
||||
SettingInitialWindowSize SettingID = 0x4
|
||||
SettingMaxFrameSize SettingID = 0x5
|
||||
SettingMaxHeaderListSize SettingID = 0x6
|
||||
SettingHeaderTableSize SettingID = 0x1
|
||||
SettingEnablePush SettingID = 0x2
|
||||
SettingMaxConcurrentStreams SettingID = 0x3
|
||||
SettingInitialWindowSize SettingID = 0x4
|
||||
SettingMaxFrameSize SettingID = 0x5
|
||||
SettingMaxHeaderListSize SettingID = 0x6
|
||||
SettingEnableConnectProtocol SettingID = 0x8
|
||||
)
|
||||
|
||||
var settingName = map[SettingID]string{
|
||||
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
|
||||
SettingEnablePush: "ENABLE_PUSH",
|
||||
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
|
||||
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
|
||||
SettingMaxFrameSize: "MAX_FRAME_SIZE",
|
||||
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
|
||||
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
|
||||
SettingEnablePush: "ENABLE_PUSH",
|
||||
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
|
||||
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
|
||||
SettingMaxFrameSize: "MAX_FRAME_SIZE",
|
||||
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
|
||||
SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
|
||||
}
|
||||
|
||||
func (s SettingID) String() string {
|
||||
@@ -173,19 +188,15 @@ func (s SettingID) String() string {
|
||||
return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s))
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidHeaderFieldName = errors.New("http2: invalid header field name")
|
||||
errInvalidHeaderFieldValue = errors.New("http2: invalid header field value")
|
||||
)
|
||||
|
||||
// validWireHeaderFieldName reports whether v is a valid header field
|
||||
// name (key). See httpguts.ValidHeaderName for the base rules.
|
||||
//
|
||||
// Further, http2 says:
|
||||
// "Just as in HTTP/1.x, header field names are strings of ASCII
|
||||
// characters that are compared in a case-insensitive
|
||||
// fashion. However, header field names MUST be converted to
|
||||
// lowercase prior to their encoding in HTTP/2. "
|
||||
//
|
||||
// "Just as in HTTP/1.x, header field names are strings of ASCII
|
||||
// characters that are compared in a case-insensitive
|
||||
// fashion. However, header field names MUST be converted to
|
||||
// lowercase prior to their encoding in HTTP/2. "
|
||||
func validWireHeaderFieldName(v string) bool {
|
||||
if len(v) == 0 {
|
||||
return false
|
||||
@@ -216,12 +227,6 @@ type stringWriter interface {
|
||||
WriteString(s string) (n int, err error)
|
||||
}
|
||||
|
||||
// A gate lets two goroutines coordinate their activities.
|
||||
type gate chan struct{}
|
||||
|
||||
func (g gate) Done() { g <- struct{}{} }
|
||||
func (g gate) Wait() { <-g }
|
||||
|
||||
// A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed).
|
||||
type closeWaiter chan struct{}
|
||||
|
||||
@@ -247,12 +252,17 @@ func (cw closeWaiter) Wait() {
|
||||
// Its buffered writer is lazily allocated as needed, to minimize
|
||||
// idle memory usage with many connections.
|
||||
type bufferedWriter struct {
|
||||
w io.Writer // immutable
|
||||
bw *bufio.Writer // non-nil when data is buffered
|
||||
_ incomparable
|
||||
conn net.Conn // immutable
|
||||
bw *bufio.Writer // non-nil when data is buffered
|
||||
byteTimeout time.Duration // immutable, WriteByteTimeout
|
||||
}
|
||||
|
||||
func newBufferedWriter(w io.Writer) *bufferedWriter {
|
||||
return &bufferedWriter{w: w}
|
||||
func newBufferedWriter(conn net.Conn, timeout time.Duration) *bufferedWriter {
|
||||
return &bufferedWriter{
|
||||
conn: conn,
|
||||
byteTimeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// bufWriterPoolBufferSize is the size of bufio.Writer's
|
||||
@@ -279,7 +289,7 @@ func (w *bufferedWriter) Available() int {
|
||||
func (w *bufferedWriter) Write(p []byte) (n int, err error) {
|
||||
if w.bw == nil {
|
||||
bw := bufWriterPool.Get().(*bufio.Writer)
|
||||
bw.Reset(w.w)
|
||||
bw.Reset((*bufferedWriterTimeoutWriter)(w))
|
||||
w.bw = bw
|
||||
}
|
||||
return w.bw.Write(p)
|
||||
@@ -297,6 +307,32 @@ func (w *bufferedWriter) Flush() error {
|
||||
return err
|
||||
}
|
||||
|
||||
type bufferedWriterTimeoutWriter bufferedWriter
|
||||
|
||||
func (w *bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) {
|
||||
return writeWithByteTimeout(w.conn, w.byteTimeout, p)
|
||||
}
|
||||
|
||||
// writeWithByteTimeout writes to conn.
|
||||
// If more than timeout passes without any bytes being written to the connection,
|
||||
// the write fails.
|
||||
func writeWithByteTimeout(conn net.Conn, timeout time.Duration, p []byte) (n int, err error) {
|
||||
if timeout <= 0 {
|
||||
return conn.Write(p)
|
||||
}
|
||||
for {
|
||||
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
nn, err := conn.Write(p[n:])
|
||||
n += nn
|
||||
if n == len(p) || nn == 0 || !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
// Either we finished the write, made no progress, or hit the deadline.
|
||||
// Whichever it is, we're done now.
|
||||
conn.SetWriteDeadline(time.Time{})
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustUint31(v int32) uint32 {
|
||||
if v < 0 || v > 2147483647 {
|
||||
panic("out of range")
|
||||
@@ -319,6 +355,7 @@ func bodyAllowedForStatus(status int) bool {
|
||||
}
|
||||
|
||||
type httpError struct {
|
||||
_ incomparable
|
||||
msg string
|
||||
timeout bool
|
||||
}
|
||||
@@ -366,19 +403,7 @@ func (s *sorter) SortStrings(ss []string) {
|
||||
s.v = save
|
||||
}
|
||||
|
||||
// validPseudoPath reports whether v is a valid :path pseudo-header
|
||||
// value. It must be either:
|
||||
//
|
||||
// *) a non-empty string starting with '/'
|
||||
// *) the string '*', for OPTIONS requests.
|
||||
//
|
||||
// For now this is only used a quick check for deciding when to clean
|
||||
// up Opaque URLs before sending requests from the Transport.
|
||||
// See golang.org/issue/16847
|
||||
//
|
||||
// We used to enforce that the path also didn't start with "//", but
|
||||
// Google's GFE accepts such paths and Chrome sends them, so ignore
|
||||
// that part of the spec. See golang.org/issue/19103.
|
||||
func validPseudoPath(v string) bool {
|
||||
return (len(v) > 0 && v[0] == '/') || v == "*"
|
||||
}
|
||||
// incomparable is a zero-width, non-comparable type. Adding it to a struct
|
||||
// makes that struct also non-comparable, and generally doesn't add
|
||||
// any size (as long as it's first).
|
||||
type incomparable [0]func()
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
// +build !go1.11
|
||||
|
||||
package http2
|
||||
|
||||
import "net/textproto"
|
||||
|
||||
func traceHasWroteHeaderField(trace *clientTrace) bool { return false }
|
||||
|
||||
func traceWroteHeaderField(trace *clientTrace, k, v string) {}
|
||||
|
||||
func traceGot1xxResponseFunc(trace *clientTrace) func(int, textproto.MIMEHeader) error {
|
||||
return nil
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build !go1.6
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func configureTransport(t1 *http.Transport) (*Transport, error) {
|
||||
return nil, errTransportVersion
|
||||
}
|
||||
|
||||
func transportExpectContinueTimeout(t1 *http.Transport) time.Duration {
|
||||
return 0
|
||||
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.7
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type contextContext interface {
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
}
|
||||
|
||||
var errCanceled = errors.New("canceled")
|
||||
|
||||
type fakeContext struct{}
|
||||
|
||||
func (fakeContext) Done() <-chan struct{} { return nil }
|
||||
func (fakeContext) Err() error { panic("should not be called") }
|
||||
|
||||
func reqContext(r *http.Request) fakeContext {
|
||||
return fakeContext{}
|
||||
}
|
||||
|
||||
func setResponseUncompressed(res *http.Response) {
|
||||
// Nothing.
|
||||
}
|
||||
|
||||
type clientTrace struct{}
|
||||
|
||||
func requestTrace(*http.Request) *clientTrace { return nil }
|
||||
func traceGetConn(*http.Request, string) {}
|
||||
func traceGotConn(*http.Request, *ClientConn) {}
|
||||
func traceFirstResponseByte(*clientTrace) {}
|
||||
func traceWroteHeaders(*clientTrace) {}
|
||||
func traceWroteRequest(*clientTrace, error) {}
|
||||
func traceGot100Continue(trace *clientTrace) {}
|
||||
func traceWait100Continue(trace *clientTrace) {}
|
||||
|
||||
func nop() {}
|
||||
|
||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx contextContext, cancel func()) {
|
||||
return nil, nop
|
||||
}
|
||||
|
||||
func contextWithCancel(ctx contextContext) (_ contextContext, cancel func()) {
|
||||
return ctx, nop
|
||||
}
|
||||
|
||||
func requestWithContext(req *http.Request, ctx contextContext) *http.Request {
|
||||
return req
|
||||
}
|
||||
|
||||
// temporary copy of Go 1.6's private tls.Config.clone:
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: c.Rand,
|
||||
Time: c.Time,
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: c.MinVersion,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *ClientConn) Ping(ctx contextContext) error {
|
||||
return cc.ping(ctx)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) Shutdown(ctx contextContext) error {
|
||||
return cc.shutdown(ctx)
|
||||
}
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration { return 0 }
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func configureServer18(h1 *http.Server, h2 *Server) error {
|
||||
// No IdleTimeout to sync prior to Go 1.8.
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldLogPanic(panicValue interface{}) bool {
|
||||
return panicValue != nil
|
||||
}
|
||||
|
||||
func reqGetBody(req *http.Request) func() (io.ReadCloser, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func reqBodyIsNoBody(io.ReadCloser) bool { return false }
|
||||
|
||||
func go18httpNoBody() io.ReadCloser { return nil } // for tests only
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.9
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func configureServer19(s *http.Server, conf *Server) error {
|
||||
// not supported prior to go1.9
|
||||
return nil
|
||||
}
|
||||
+26
-5
@@ -17,6 +17,7 @@ type pipe struct {
|
||||
mu sync.Mutex
|
||||
c sync.Cond // c.L lazily initialized to &p.mu
|
||||
b pipeBuffer // nil when done reading
|
||||
unread int // bytes unread when done
|
||||
err error // read error once empty. non-nil means closed.
|
||||
breakErr error // immediate read error (caller doesn't see rest of b)
|
||||
donec chan struct{} // closed on error
|
||||
@@ -29,11 +30,22 @@ type pipeBuffer interface {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// setBuffer initializes the pipe buffer.
|
||||
// It has no effect if the pipe is already closed.
|
||||
func (p *pipe) setBuffer(b pipeBuffer) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.err != nil || p.breakErr != nil {
|
||||
return
|
||||
}
|
||||
p.b = b
|
||||
}
|
||||
|
||||
func (p *pipe) Len() int {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.b == nil {
|
||||
return 0
|
||||
return p.unread
|
||||
}
|
||||
return p.b.Len()
|
||||
}
|
||||
@@ -65,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
var errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
var (
|
||||
errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
|
||||
)
|
||||
|
||||
// Write copies bytes from p into the buffer and wakes a reader.
|
||||
// It is an error to write more data than the buffer can hold.
|
||||
@@ -76,11 +91,14 @@ func (p *pipe) Write(d []byte) (n int, err error) {
|
||||
p.c.L = &p.mu
|
||||
}
|
||||
defer p.c.Signal()
|
||||
if p.err != nil {
|
||||
if p.err != nil || p.breakErr != nil {
|
||||
return 0, errClosedPipeWrite
|
||||
}
|
||||
if p.breakErr != nil {
|
||||
return len(d), nil // discard when there is no reader
|
||||
// pipe.setBuffer is never invoked, leaving the buffer uninitialized.
|
||||
// We shouldn't try to write to an uninitialized pipe,
|
||||
// but returning an error is better than panicking.
|
||||
if p.b == nil {
|
||||
return 0, errUninitializedPipeWrite
|
||||
}
|
||||
return p.b.Write(d)
|
||||
}
|
||||
@@ -117,6 +135,9 @@ func (p *pipe) closeWithError(dst *error, err error, fn func()) {
|
||||
}
|
||||
p.readFn = fn
|
||||
if dst == &p.breakErr {
|
||||
if p.b != nil {
|
||||
p.unread += p.b.Len()
|
||||
}
|
||||
p.b = nil
|
||||
}
|
||||
*dst = err
|
||||
|
||||
+843
-392
File diff suppressed because it is too large
Load Diff
+2004
-1018
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
const nextProtoUnencryptedHTTP2 = "unencrypted_http2"
|
||||
|
||||
// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn.
|
||||
//
|
||||
// TLSNextProto functions accept a *tls.Conn.
|
||||
//
|
||||
// When passing an unencrypted HTTP/2 connection to a TLSNextProto function,
|
||||
// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection.
|
||||
// To be extra careful about mistakes (accidentally dropping TLS encryption in a place
|
||||
// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method
|
||||
// that returns the actual connection we want to use.
|
||||
func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) {
|
||||
conner, ok := tc.NetConn().(interface {
|
||||
UnencryptedNetConn() net.Conn
|
||||
})
|
||||
if !ok {
|
||||
return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff")
|
||||
}
|
||||
return conner.UnencryptedNetConn(), nil
|
||||
}
|
||||
+18
-2
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/internal/httpcommon"
|
||||
)
|
||||
|
||||
// writeFramer is implemented by any type that is used to write frames.
|
||||
@@ -131,6 +132,16 @@ func (se StreamError) writeFrame(ctx writeContext) error {
|
||||
|
||||
func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
|
||||
|
||||
type writePing struct {
|
||||
data [8]byte
|
||||
}
|
||||
|
||||
func (w writePing) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WritePing(false, w.data)
|
||||
}
|
||||
|
||||
func (w writePing) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.data) <= max }
|
||||
|
||||
type writePingAck struct{ pf *PingFrame }
|
||||
|
||||
func (w writePingAck) writeFrame(ctx writeContext) error {
|
||||
@@ -329,7 +340,7 @@ func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
|
||||
}
|
||||
|
||||
// encodeHeaders encodes an http.Header. If keys is not nil, then (k, h[k])
|
||||
// is encoded only only if k is in keys.
|
||||
// is encoded only if k is in keys.
|
||||
func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
|
||||
if keys == nil {
|
||||
sorter := sorterPool.Get().(*sorter)
|
||||
@@ -341,7 +352,12 @@ func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
|
||||
}
|
||||
for _, k := range keys {
|
||||
vv := h[k]
|
||||
k = lowerHeader(k)
|
||||
k, ascii := httpcommon.LowerHeader(k)
|
||||
if !ascii {
|
||||
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
|
||||
// field names have to be ASCII characters (just as in HTTP/1.x).
|
||||
continue
|
||||
}
|
||||
if !validWireHeaderFieldName(k) {
|
||||
// Skip it as backup paranoia. Per
|
||||
// golang.org/issue/14048, these should
|
||||
|
||||
+62
-16
@@ -32,7 +32,8 @@ type WriteScheduler interface {
|
||||
|
||||
// Pop dequeues the next frame to write. Returns false if no frames can
|
||||
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
||||
// order they are Push'd.
|
||||
// order they are Push'd, except RST_STREAM frames. No frames should be
|
||||
// discarded except by CloseStream.
|
||||
Pop() (wr FrameWriteRequest, ok bool)
|
||||
}
|
||||
|
||||
@@ -41,6 +42,8 @@ type OpenStreamOptions struct {
|
||||
// PusherID is zero if the stream was initiated by the client. Otherwise,
|
||||
// PusherID names the stream that pushed the newly opened stream.
|
||||
PusherID uint32
|
||||
// priority is used to set the priority of the newly opened stream.
|
||||
priority PriorityParam
|
||||
}
|
||||
|
||||
// FrameWriteRequest is a request to write a frame.
|
||||
@@ -52,6 +55,7 @@ type FrameWriteRequest struct {
|
||||
|
||||
// stream is the stream on which this frame will be written.
|
||||
// nil for non-stream frames like PING and SETTINGS.
|
||||
// nil for RST_STREAM streams, which use the StreamError.StreamID field instead.
|
||||
stream *stream
|
||||
|
||||
// done, if non-nil, must be a buffered channel with space for
|
||||
@@ -76,6 +80,12 @@ func (wr FrameWriteRequest) StreamID() uint32 {
|
||||
return wr.stream.id
|
||||
}
|
||||
|
||||
// isControl reports whether wr is a control frame for MaxQueuedControlFrames
|
||||
// purposes. That includes non-stream frames and RST_STREAM frames.
|
||||
func (wr FrameWriteRequest) isControl() bool {
|
||||
return wr.stream == nil
|
||||
}
|
||||
|
||||
// DataSize returns the number of flow control bytes that must be consumed
|
||||
// to write this entire frame. This is 0 for non-DATA frames.
|
||||
func (wr FrameWriteRequest) DataSize() int {
|
||||
@@ -175,44 +185,75 @@ func (wr *FrameWriteRequest) replyToWriter(err error) {
|
||||
}
|
||||
|
||||
// writeQueue is used by implementations of WriteScheduler.
|
||||
//
|
||||
// Each writeQueue contains a queue of FrameWriteRequests, meant to store all
|
||||
// FrameWriteRequests associated with a given stream. This is implemented as a
|
||||
// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done
|
||||
// by incrementing currPos of currQueue. Adding an item is done by appending it
|
||||
// to the nextQueue. If currQueue is empty when trying to remove an item, we
|
||||
// can swap currQueue and nextQueue to remedy the situation.
|
||||
// This two-stage queue is analogous to the use of two lists in Okasaki's
|
||||
// purely functional queue but without the overhead of reversing the list when
|
||||
// swapping stages.
|
||||
//
|
||||
// writeQueue also contains prev and next, this can be used by implementations
|
||||
// of WriteScheduler to construct data structures that represent the order of
|
||||
// writing between different streams (e.g. circular linked list).
|
||||
type writeQueue struct {
|
||||
s []FrameWriteRequest
|
||||
currQueue []FrameWriteRequest
|
||||
nextQueue []FrameWriteRequest
|
||||
currPos int
|
||||
|
||||
prev, next *writeQueue
|
||||
}
|
||||
|
||||
func (q *writeQueue) empty() bool { return len(q.s) == 0 }
|
||||
func (q *writeQueue) empty() bool {
|
||||
return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0
|
||||
}
|
||||
|
||||
func (q *writeQueue) push(wr FrameWriteRequest) {
|
||||
q.s = append(q.s, wr)
|
||||
q.nextQueue = append(q.nextQueue, wr)
|
||||
}
|
||||
|
||||
func (q *writeQueue) shift() FrameWriteRequest {
|
||||
if len(q.s) == 0 {
|
||||
if q.empty() {
|
||||
panic("invalid use of queue")
|
||||
}
|
||||
wr := q.s[0]
|
||||
// TODO: less copy-happy queue.
|
||||
copy(q.s, q.s[1:])
|
||||
q.s[len(q.s)-1] = FrameWriteRequest{}
|
||||
q.s = q.s[:len(q.s)-1]
|
||||
if q.currPos >= len(q.currQueue) {
|
||||
q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0]
|
||||
}
|
||||
wr := q.currQueue[q.currPos]
|
||||
q.currQueue[q.currPos] = FrameWriteRequest{}
|
||||
q.currPos++
|
||||
return wr
|
||||
}
|
||||
|
||||
func (q *writeQueue) peek() *FrameWriteRequest {
|
||||
if q.currPos < len(q.currQueue) {
|
||||
return &q.currQueue[q.currPos]
|
||||
}
|
||||
if len(q.nextQueue) > 0 {
|
||||
return &q.nextQueue[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// consume consumes up to n bytes from q.s[0]. If the frame is
|
||||
// entirely consumed, it is removed from the queue. If the frame
|
||||
// is partially consumed, the frame is kept with the consumed
|
||||
// bytes removed. Returns true iff any bytes were consumed.
|
||||
func (q *writeQueue) consume(n int32) (FrameWriteRequest, bool) {
|
||||
if len(q.s) == 0 {
|
||||
if q.empty() {
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
consumed, rest, numresult := q.s[0].Consume(n)
|
||||
consumed, rest, numresult := q.peek().Consume(n)
|
||||
switch numresult {
|
||||
case 0:
|
||||
return FrameWriteRequest{}, false
|
||||
case 1:
|
||||
q.shift()
|
||||
case 2:
|
||||
q.s[0] = rest
|
||||
*q.peek() = rest
|
||||
}
|
||||
return consumed, true
|
||||
}
|
||||
@@ -221,10 +262,15 @@ type writeQueuePool []*writeQueue
|
||||
|
||||
// put inserts an unused writeQueue into the pool.
|
||||
func (p *writeQueuePool) put(q *writeQueue) {
|
||||
for i := range q.s {
|
||||
q.s[i] = FrameWriteRequest{}
|
||||
for i := range q.currQueue {
|
||||
q.currQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
q.s = q.s[:0]
|
||||
for i := range q.nextQueue {
|
||||
q.nextQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
q.currQueue = q.currQueue[:0]
|
||||
q.nextQueue = q.nextQueue[:0]
|
||||
q.currPos = 0
|
||||
*p = append(*p, q)
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+61
-63
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// RFC 7540, Section 5.3.5: the default weight is 16.
|
||||
const priorityDefaultWeight = 15 // 16 = 15 + 1
|
||||
const priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
|
||||
|
||||
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
||||
type PriorityWriteSchedulerConfig struct {
|
||||
@@ -66,8 +66,8 @@ func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler
|
||||
}
|
||||
}
|
||||
|
||||
ws := &priorityWriteScheduler{
|
||||
nodes: make(map[uint32]*priorityNode),
|
||||
ws := &priorityWriteSchedulerRFC7540{
|
||||
nodes: make(map[uint32]*priorityNodeRFC7540),
|
||||
maxClosedNodesInTree: cfg.MaxClosedNodesInTree,
|
||||
maxIdleNodesInTree: cfg.MaxIdleNodesInTree,
|
||||
enableWriteThrottle: cfg.ThrottleOutOfOrderWrites,
|
||||
@@ -81,32 +81,32 @@ func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler
|
||||
return ws
|
||||
}
|
||||
|
||||
type priorityNodeState int
|
||||
type priorityNodeStateRFC7540 int
|
||||
|
||||
const (
|
||||
priorityNodeOpen priorityNodeState = iota
|
||||
priorityNodeClosed
|
||||
priorityNodeIdle
|
||||
priorityNodeOpenRFC7540 priorityNodeStateRFC7540 = iota
|
||||
priorityNodeClosedRFC7540
|
||||
priorityNodeIdleRFC7540
|
||||
)
|
||||
|
||||
// priorityNode is a node in an HTTP/2 priority tree.
|
||||
// priorityNodeRFC7540 is a node in an HTTP/2 priority tree.
|
||||
// Each node is associated with a single stream ID.
|
||||
// See RFC 7540, Section 5.3.
|
||||
type priorityNode struct {
|
||||
q writeQueue // queue of pending frames to write
|
||||
id uint32 // id of the stream, or 0 for the root of the tree
|
||||
weight uint8 // the actual weight is weight+1, so the value is in [1,256]
|
||||
state priorityNodeState // open | closed | idle
|
||||
bytes int64 // number of bytes written by this node, or 0 if closed
|
||||
subtreeBytes int64 // sum(node.bytes) of all nodes in this subtree
|
||||
type priorityNodeRFC7540 struct {
|
||||
q writeQueue // queue of pending frames to write
|
||||
id uint32 // id of the stream, or 0 for the root of the tree
|
||||
weight uint8 // the actual weight is weight+1, so the value is in [1,256]
|
||||
state priorityNodeStateRFC7540 // open | closed | idle
|
||||
bytes int64 // number of bytes written by this node, or 0 if closed
|
||||
subtreeBytes int64 // sum(node.bytes) of all nodes in this subtree
|
||||
|
||||
// These links form the priority tree.
|
||||
parent *priorityNode
|
||||
kids *priorityNode // start of the kids list
|
||||
prev, next *priorityNode // doubly-linked list of siblings
|
||||
parent *priorityNodeRFC7540
|
||||
kids *priorityNodeRFC7540 // start of the kids list
|
||||
prev, next *priorityNodeRFC7540 // doubly-linked list of siblings
|
||||
}
|
||||
|
||||
func (n *priorityNode) setParent(parent *priorityNode) {
|
||||
func (n *priorityNodeRFC7540) setParent(parent *priorityNodeRFC7540) {
|
||||
if n == parent {
|
||||
panic("setParent to self")
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (n *priorityNode) setParent(parent *priorityNode) {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *priorityNode) addBytes(b int64) {
|
||||
func (n *priorityNodeRFC7540) addBytes(b int64) {
|
||||
n.bytes += b
|
||||
for ; n != nil; n = n.parent {
|
||||
n.subtreeBytes += b
|
||||
@@ -149,12 +149,12 @@ func (n *priorityNode) addBytes(b int64) {
|
||||
}
|
||||
|
||||
// walkReadyInOrder iterates over the tree in priority order, calling f for each node
|
||||
// with a non-empty write queue. When f returns true, this funcion returns true and the
|
||||
// with a non-empty write queue. When f returns true, this function returns true and the
|
||||
// walk halts. tmp is used as scratch space for sorting.
|
||||
//
|
||||
// f(n, openParent) takes two arguments: the node to visit, n, and a bool that is true
|
||||
// if any ancestor p of n is still open (ignoring the root node).
|
||||
func (n *priorityNode) walkReadyInOrder(openParent bool, tmp *[]*priorityNode, f func(*priorityNode, bool) bool) bool {
|
||||
func (n *priorityNodeRFC7540) walkReadyInOrder(openParent bool, tmp *[]*priorityNodeRFC7540, f func(*priorityNodeRFC7540, bool) bool) bool {
|
||||
if !n.q.empty() && f(n, openParent) {
|
||||
return true
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func (n *priorityNode) walkReadyInOrder(openParent bool, tmp *[]*priorityNode, f
|
||||
// Don't consider the root "open" when updating openParent since
|
||||
// we can't send data frames on the root stream (only control frames).
|
||||
if n.id != 0 {
|
||||
openParent = openParent || (n.state == priorityNodeOpen)
|
||||
openParent = openParent || (n.state == priorityNodeOpenRFC7540)
|
||||
}
|
||||
|
||||
// Common case: only one kid or all kids have the same weight.
|
||||
@@ -195,7 +195,7 @@ func (n *priorityNode) walkReadyInOrder(openParent bool, tmp *[]*priorityNode, f
|
||||
*tmp = append(*tmp, n.kids)
|
||||
n.kids.setParent(nil)
|
||||
}
|
||||
sort.Sort(sortPriorityNodeSiblings(*tmp))
|
||||
sort.Sort(sortPriorityNodeSiblingsRFC7540(*tmp))
|
||||
for i := len(*tmp) - 1; i >= 0; i-- {
|
||||
(*tmp)[i].setParent(n) // setParent inserts at the head of n.kids
|
||||
}
|
||||
@@ -207,15 +207,15 @@ func (n *priorityNode) walkReadyInOrder(openParent bool, tmp *[]*priorityNode, f
|
||||
return false
|
||||
}
|
||||
|
||||
type sortPriorityNodeSiblings []*priorityNode
|
||||
type sortPriorityNodeSiblingsRFC7540 []*priorityNodeRFC7540
|
||||
|
||||
func (z sortPriorityNodeSiblings) Len() int { return len(z) }
|
||||
func (z sortPriorityNodeSiblings) Swap(i, k int) { z[i], z[k] = z[k], z[i] }
|
||||
func (z sortPriorityNodeSiblings) Less(i, k int) bool {
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Len() int { return len(z) }
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i] }
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool {
|
||||
// Prefer the subtree that has sent fewer bytes relative to its weight.
|
||||
// See sections 5.3.2 and 5.3.4.
|
||||
wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes)
|
||||
wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes)
|
||||
wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes)
|
||||
wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes)
|
||||
if bi == 0 && bk == 0 {
|
||||
return wi >= wk
|
||||
}
|
||||
@@ -225,13 +225,13 @@ func (z sortPriorityNodeSiblings) Less(i, k int) bool {
|
||||
return bi/bk <= wi/wk
|
||||
}
|
||||
|
||||
type priorityWriteScheduler struct {
|
||||
type priorityWriteSchedulerRFC7540 struct {
|
||||
// root is the root of the priority tree, where root.id = 0.
|
||||
// The root queues control frames that are not associated with any stream.
|
||||
root priorityNode
|
||||
root priorityNodeRFC7540
|
||||
|
||||
// nodes maps stream ids to priority tree nodes.
|
||||
nodes map[uint32]*priorityNode
|
||||
nodes map[uint32]*priorityNodeRFC7540
|
||||
|
||||
// maxID is the maximum stream id in nodes.
|
||||
maxID uint32
|
||||
@@ -239,7 +239,7 @@ type priorityWriteScheduler struct {
|
||||
// lists of nodes that have been closed or are idle, but are kept in
|
||||
// the tree for improved prioritization. When the lengths exceed either
|
||||
// maxClosedNodesInTree or maxIdleNodesInTree, old nodes are discarded.
|
||||
closedNodes, idleNodes []*priorityNode
|
||||
closedNodes, idleNodes []*priorityNodeRFC7540
|
||||
|
||||
// From the config.
|
||||
maxClosedNodesInTree int
|
||||
@@ -248,19 +248,19 @@ type priorityWriteScheduler struct {
|
||||
enableWriteThrottle bool
|
||||
|
||||
// tmp is scratch space for priorityNode.walkReadyInOrder to reduce allocations.
|
||||
tmp []*priorityNode
|
||||
tmp []*priorityNodeRFC7540
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
func (ws *priorityWriteSchedulerRFC7540) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
// The stream may be currently idle but cannot be opened or closed.
|
||||
if curr := ws.nodes[streamID]; curr != nil {
|
||||
if curr.state != priorityNodeIdle {
|
||||
if curr.state != priorityNodeIdleRFC7540 {
|
||||
panic(fmt.Sprintf("stream %d already opened", streamID))
|
||||
}
|
||||
curr.state = priorityNodeOpen
|
||||
curr.state = priorityNodeOpenRFC7540
|
||||
return
|
||||
}
|
||||
|
||||
@@ -272,11 +272,11 @@ func (ws *priorityWriteScheduler) OpenStream(streamID uint32, options OpenStream
|
||||
if parent == nil {
|
||||
parent = &ws.root
|
||||
}
|
||||
n := &priorityNode{
|
||||
n := &priorityNodeRFC7540{
|
||||
q: *ws.queuePool.get(),
|
||||
id: streamID,
|
||||
weight: priorityDefaultWeight,
|
||||
state: priorityNodeOpen,
|
||||
weight: priorityDefaultWeightRFC7540,
|
||||
state: priorityNodeOpenRFC7540,
|
||||
}
|
||||
n.setParent(parent)
|
||||
ws.nodes[streamID] = n
|
||||
@@ -285,24 +285,23 @@ func (ws *priorityWriteScheduler) OpenStream(streamID uint32, options OpenStream
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) CloseStream(streamID uint32) {
|
||||
func (ws *priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) {
|
||||
if streamID == 0 {
|
||||
panic("violation of WriteScheduler interface: cannot close stream 0")
|
||||
}
|
||||
if ws.nodes[streamID] == nil {
|
||||
panic(fmt.Sprintf("violation of WriteScheduler interface: unknown stream %d", streamID))
|
||||
}
|
||||
if ws.nodes[streamID].state != priorityNodeOpen {
|
||||
if ws.nodes[streamID].state != priorityNodeOpenRFC7540 {
|
||||
panic(fmt.Sprintf("violation of WriteScheduler interface: stream %d already closed", streamID))
|
||||
}
|
||||
|
||||
n := ws.nodes[streamID]
|
||||
n.state = priorityNodeClosed
|
||||
n.state = priorityNodeClosedRFC7540
|
||||
n.addBytes(-n.bytes)
|
||||
|
||||
q := n.q
|
||||
ws.queuePool.put(&q)
|
||||
n.q.s = nil
|
||||
if ws.maxClosedNodesInTree > 0 {
|
||||
ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n)
|
||||
} else {
|
||||
@@ -310,7 +309,7 @@ func (ws *priorityWriteScheduler) CloseStream(streamID uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
func (ws *priorityWriteSchedulerRFC7540) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
if streamID == 0 {
|
||||
panic("adjustPriority on root")
|
||||
}
|
||||
@@ -324,11 +323,11 @@ func (ws *priorityWriteScheduler) AdjustStream(streamID uint32, priority Priorit
|
||||
return
|
||||
}
|
||||
ws.maxID = streamID
|
||||
n = &priorityNode{
|
||||
n = &priorityNodeRFC7540{
|
||||
q: *ws.queuePool.get(),
|
||||
id: streamID,
|
||||
weight: priorityDefaultWeight,
|
||||
state: priorityNodeIdle,
|
||||
weight: priorityDefaultWeightRFC7540,
|
||||
state: priorityNodeIdleRFC7540,
|
||||
}
|
||||
n.setParent(&ws.root)
|
||||
ws.nodes[streamID] = n
|
||||
@@ -340,7 +339,7 @@ func (ws *priorityWriteScheduler) AdjustStream(streamID uint32, priority Priorit
|
||||
parent := ws.nodes[priority.StreamDep]
|
||||
if parent == nil {
|
||||
n.setParent(&ws.root)
|
||||
n.weight = priorityDefaultWeight
|
||||
n.weight = priorityDefaultWeightRFC7540
|
||||
return
|
||||
}
|
||||
|
||||
@@ -381,18 +380,17 @@ func (ws *priorityWriteScheduler) AdjustStream(streamID uint32, priority Priorit
|
||||
n.weight = priority.Weight
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
var n *priorityNode
|
||||
if id := wr.StreamID(); id == 0 {
|
||||
func (ws *priorityWriteSchedulerRFC7540) Push(wr FrameWriteRequest) {
|
||||
var n *priorityNodeRFC7540
|
||||
if wr.isControl() {
|
||||
n = &ws.root
|
||||
} else {
|
||||
id := wr.StreamID()
|
||||
n = ws.nodes[id]
|
||||
if n == nil {
|
||||
// id is an idle or closed stream. wr should not be a HEADERS or
|
||||
// DATA frame. However, wr can be a RST_STREAM. In this case, we
|
||||
// push wr onto the root, rather than creating a new priorityNode,
|
||||
// since RST_STREAM is tiny and the stream's priority is unknown
|
||||
// anyway. See issue #17919.
|
||||
// DATA frame. In other case, we push wr onto the root, rather
|
||||
// than creating a new priorityNode.
|
||||
if wr.DataSize() > 0 {
|
||||
panic("add DATA on non-open stream")
|
||||
}
|
||||
@@ -402,8 +400,8 @@ func (ws *priorityWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
n.q.push(wr)
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) Pop() (wr FrameWriteRequest, ok bool) {
|
||||
ws.root.walkReadyInOrder(false, &ws.tmp, func(n *priorityNode, openParent bool) bool {
|
||||
func (ws *priorityWriteSchedulerRFC7540) Pop() (wr FrameWriteRequest, ok bool) {
|
||||
ws.root.walkReadyInOrder(false, &ws.tmp, func(n *priorityNodeRFC7540, openParent bool) bool {
|
||||
limit := int32(math.MaxInt32)
|
||||
if openParent {
|
||||
limit = ws.writeThrottleLimit
|
||||
@@ -429,7 +427,7 @@ func (ws *priorityWriteScheduler) Pop() (wr FrameWriteRequest, ok bool) {
|
||||
return wr, ok
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) addClosedOrIdleNode(list *[]*priorityNode, maxSize int, n *priorityNode) {
|
||||
func (ws *priorityWriteSchedulerRFC7540) addClosedOrIdleNode(list *[]*priorityNodeRFC7540, maxSize int, n *priorityNodeRFC7540) {
|
||||
if maxSize == 0 {
|
||||
return
|
||||
}
|
||||
@@ -443,9 +441,9 @@ func (ws *priorityWriteScheduler) addClosedOrIdleNode(list *[]*priorityNode, max
|
||||
*list = append(*list, n)
|
||||
}
|
||||
|
||||
func (ws *priorityWriteScheduler) removeNode(n *priorityNode) {
|
||||
for k := n.kids; k != nil; k = k.next {
|
||||
k.setParent(n.parent)
|
||||
func (ws *priorityWriteSchedulerRFC7540) removeNode(n *priorityNodeRFC7540) {
|
||||
for n.kids != nil {
|
||||
n.kids.setParent(n.parent)
|
||||
}
|
||||
n.setParent(nil)
|
||||
delete(ws.nodes, n.id)
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type streamMetadata struct {
|
||||
location *writeQueue
|
||||
priority PriorityParam
|
||||
}
|
||||
|
||||
type priorityWriteSchedulerRFC9218 struct {
|
||||
// control contains control frames (SETTINGS, PING, etc.).
|
||||
control writeQueue
|
||||
|
||||
// heads contain the head of a circular list of streams.
|
||||
// We put these heads within a nested array that represents urgency and
|
||||
// incremental, as defined in
|
||||
// https://www.rfc-editor.org/rfc/rfc9218.html#name-priority-parameters.
|
||||
// 8 represents u=0 up to u=7, and 2 represents i=false and i=true.
|
||||
heads [8][2]*writeQueue
|
||||
|
||||
// streams contains a mapping between each stream ID and their metadata, so
|
||||
// we can quickly locate them when needing to, for example, adjust their
|
||||
// priority.
|
||||
streams map[uint32]streamMetadata
|
||||
|
||||
// queuePool are empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
|
||||
// prioritizeIncremental is used to determine whether we should prioritize
|
||||
// incremental streams or not, when urgency is the same in a given Pop()
|
||||
// call.
|
||||
prioritizeIncremental bool
|
||||
}
|
||||
|
||||
func newPriorityWriteSchedulerRFC9218() WriteScheduler {
|
||||
ws := &priorityWriteSchedulerRFC9218{
|
||||
streams: make(map[uint32]streamMetadata),
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt OpenStreamOptions) {
|
||||
if ws.streams[streamID].location != nil {
|
||||
panic(fmt.Errorf("stream %d already opened", streamID))
|
||||
}
|
||||
q := ws.queuePool.get()
|
||||
ws.streams[streamID] = streamMetadata{
|
||||
location: q,
|
||||
priority: opt.priority,
|
||||
}
|
||||
|
||||
u, i := opt.priority.urgency, opt.priority.incremental
|
||||
if ws.heads[u][i] == nil {
|
||||
ws.heads[u][i] = q
|
||||
q.next = q
|
||||
q.prev = q
|
||||
} else {
|
||||
// Queues are stored in a ring.
|
||||
// Insert the new stream before ws.head, putting it at the end of the list.
|
||||
q.prev = ws.heads[u][i].prev
|
||||
q.next = ws.heads[u][i]
|
||||
q.prev.next = q
|
||||
q.next.prev = q
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) CloseStream(streamID uint32) {
|
||||
metadata := ws.streams[streamID]
|
||||
q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.next == q {
|
||||
// This was the only open stream.
|
||||
ws.heads[u][i] = nil
|
||||
} else {
|
||||
q.prev.next = q.next
|
||||
q.next.prev = q.prev
|
||||
if ws.heads[u][i] == q {
|
||||
ws.heads[u][i] = q.next
|
||||
}
|
||||
}
|
||||
delete(ws.streams, streamID)
|
||||
ws.queuePool.put(q)
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
metadata := ws.streams[streamID]
|
||||
q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove stream from current location.
|
||||
if q.next == q {
|
||||
// This was the only open stream.
|
||||
ws.heads[u][i] = nil
|
||||
} else {
|
||||
q.prev.next = q.next
|
||||
q.next.prev = q.prev
|
||||
if ws.heads[u][i] == q {
|
||||
ws.heads[u][i] = q.next
|
||||
}
|
||||
}
|
||||
|
||||
// Insert stream to the new queue.
|
||||
u, i = priority.urgency, priority.incremental
|
||||
if ws.heads[u][i] == nil {
|
||||
ws.heads[u][i] = q
|
||||
q.next = q
|
||||
q.prev = q
|
||||
} else {
|
||||
// Queues are stored in a ring.
|
||||
// Insert the new stream before ws.head, putting it at the end of the list.
|
||||
q.prev = ws.heads[u][i].prev
|
||||
q.next = ws.heads[u][i]
|
||||
q.prev.next = q
|
||||
q.next.prev = q
|
||||
}
|
||||
|
||||
// Update the metadata.
|
||||
ws.streams[streamID] = streamMetadata{
|
||||
location: q,
|
||||
priority: priority,
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) Push(wr FrameWriteRequest) {
|
||||
if wr.isControl() {
|
||||
ws.control.push(wr)
|
||||
return
|
||||
}
|
||||
q := ws.streams[wr.StreamID()].location
|
||||
if q == nil {
|
||||
// This is a closed stream.
|
||||
// wr should not be a HEADERS or DATA frame.
|
||||
// We push the request onto the control queue.
|
||||
if wr.DataSize() > 0 {
|
||||
panic("add DATA on non-open stream")
|
||||
}
|
||||
ws.control.push(wr)
|
||||
return
|
||||
}
|
||||
q.push(wr)
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) Pop() (FrameWriteRequest, bool) {
|
||||
// Control and RST_STREAM frames first.
|
||||
if !ws.control.empty() {
|
||||
return ws.control.shift(), true
|
||||
}
|
||||
|
||||
// On the next Pop(), we want to prioritize incremental if we prioritized
|
||||
// non-incremental request of the same urgency this time. Vice-versa.
|
||||
// i.e. when there are incremental and non-incremental requests at the same
|
||||
// priority, we give 50% of our bandwidth to the incremental ones in
|
||||
// aggregate and 50% to the first non-incremental one (since
|
||||
// non-incremental streams do not use round-robin writes).
|
||||
ws.prioritizeIncremental = !ws.prioritizeIncremental
|
||||
|
||||
// Always prioritize lowest u (i.e. highest urgency level).
|
||||
for u := range ws.heads {
|
||||
for i := range ws.heads[u] {
|
||||
// When we want to prioritize incremental, we try to pop i=true
|
||||
// first before i=false when u is the same.
|
||||
if ws.prioritizeIncremental {
|
||||
i = (i + 1) % 2
|
||||
}
|
||||
q := ws.heads[u][i]
|
||||
if q == nil {
|
||||
continue
|
||||
}
|
||||
for {
|
||||
if wr, ok := q.consume(math.MaxInt32); ok {
|
||||
if i == 1 {
|
||||
// For incremental streams, we update head to q.next so
|
||||
// we can round-robin between multiple streams that can
|
||||
// immediately benefit from partial writes.
|
||||
ws.heads[u][i] = q.next
|
||||
} else {
|
||||
// For non-incremental streams, we try to finish one to
|
||||
// completion rather than doing round-robin. However,
|
||||
// we update head here so that if q.consume() is !ok
|
||||
// (e.g. the stream has no more frame to consume), head
|
||||
// is updated to the next q that has frames to consume
|
||||
// on future iterations. This way, we do not prioritize
|
||||
// writing to unavailable stream on next Pop() calls,
|
||||
// preventing head-of-line blocking.
|
||||
ws.heads[u][i] = q
|
||||
}
|
||||
return wr, true
|
||||
}
|
||||
q = q.next
|
||||
if q == ws.heads[u][i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
+10
-5
@@ -19,7 +19,8 @@ type randomWriteScheduler struct {
|
||||
zero writeQueue
|
||||
|
||||
// sq contains the stream-specific queues, keyed by stream ID.
|
||||
// When a stream is idle or closed, it's deleted from the map.
|
||||
// When a stream is idle, closed, or emptied, it's deleted
|
||||
// from the map.
|
||||
sq map[uint32]*writeQueue
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
@@ -44,11 +45,11 @@ func (ws *randomWriteScheduler) AdjustStream(streamID uint32, priority PriorityP
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
id := wr.StreamID()
|
||||
if id == 0 {
|
||||
if wr.isControl() {
|
||||
ws.zero.push(wr)
|
||||
return
|
||||
}
|
||||
id := wr.StreamID()
|
||||
q, ok := ws.sq[id]
|
||||
if !ok {
|
||||
q = ws.queuePool.get()
|
||||
@@ -58,13 +59,17 @@ func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) Pop() (FrameWriteRequest, bool) {
|
||||
// Control frames first.
|
||||
// Control and RST_STREAM frames first.
|
||||
if !ws.zero.empty() {
|
||||
return ws.zero.shift(), true
|
||||
}
|
||||
// Iterate over all non-idle streams until finding one that can be consumed.
|
||||
for _, q := range ws.sq {
|
||||
for streamID, q := range ws.sq {
|
||||
if wr, ok := q.consume(math.MaxInt32); ok {
|
||||
if q.empty() {
|
||||
delete(ws.sq, streamID)
|
||||
ws.queuePool.put(q)
|
||||
}
|
||||
return wr, true
|
||||
}
|
||||
}
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
// 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 http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type roundRobinWriteScheduler struct {
|
||||
// control contains control frames (SETTINGS, PING, etc.).
|
||||
control writeQueue
|
||||
|
||||
// streams maps stream ID to a queue.
|
||||
streams map[uint32]*writeQueue
|
||||
|
||||
// stream queues are stored in a circular linked list.
|
||||
// head is the next stream to write, or nil if there are no streams open.
|
||||
head *writeQueue
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
}
|
||||
|
||||
// newRoundRobinWriteScheduler constructs a new write scheduler.
|
||||
// The round robin scheduler prioritizes control frames
|
||||
// like SETTINGS and PING over DATA frames.
|
||||
// When there are no control frames to send, it performs a round-robin
|
||||
// selection from the ready streams.
|
||||
func newRoundRobinWriteScheduler() WriteScheduler {
|
||||
ws := &roundRobinWriteScheduler{
|
||||
streams: make(map[uint32]*writeQueue),
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
if ws.streams[streamID] != nil {
|
||||
panic(fmt.Errorf("stream %d already opened", streamID))
|
||||
}
|
||||
q := ws.queuePool.get()
|
||||
ws.streams[streamID] = q
|
||||
if ws.head == nil {
|
||||
ws.head = q
|
||||
q.next = q
|
||||
q.prev = q
|
||||
} else {
|
||||
// Queues are stored in a ring.
|
||||
// Insert the new stream before ws.head, putting it at the end of the list.
|
||||
q.prev = ws.head.prev
|
||||
q.next = ws.head
|
||||
q.prev.next = q
|
||||
q.next.prev = q
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) CloseStream(streamID uint32) {
|
||||
q := ws.streams[streamID]
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.next == q {
|
||||
// This was the only open stream.
|
||||
ws.head = nil
|
||||
} else {
|
||||
q.prev.next = q.next
|
||||
q.next.prev = q.prev
|
||||
if ws.head == q {
|
||||
ws.head = q.next
|
||||
}
|
||||
}
|
||||
delete(ws.streams, streamID)
|
||||
ws.queuePool.put(q)
|
||||
}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
if wr.isControl() {
|
||||
ws.control.push(wr)
|
||||
return
|
||||
}
|
||||
q := ws.streams[wr.StreamID()]
|
||||
if q == nil {
|
||||
// This is a closed stream.
|
||||
// wr should not be a HEADERS or DATA frame.
|
||||
// We push the request onto the control queue.
|
||||
if wr.DataSize() > 0 {
|
||||
panic("add DATA on non-open stream")
|
||||
}
|
||||
ws.control.push(wr)
|
||||
return
|
||||
}
|
||||
q.push(wr)
|
||||
}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) Pop() (FrameWriteRequest, bool) {
|
||||
// Control and RST_STREAM frames first.
|
||||
if !ws.control.empty() {
|
||||
return ws.control.shift(), true
|
||||
}
|
||||
if ws.head == nil {
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
q := ws.head
|
||||
for {
|
||||
if wr, ok := q.consume(math.MaxInt32); ok {
|
||||
ws.head = q.next
|
||||
return wr, true
|
||||
}
|
||||
q = q.next
|
||||
if q == ws.head {
|
||||
break
|
||||
}
|
||||
}
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
|
||||
package idna
|
||||
|
||||
// Transitional processing is disabled by default in Go 1.18.
|
||||
// https://golang.org/issue/47510
|
||||
const transitionalLookup = false
|
||||
Generated
Vendored
+81
-44
@@ -4,14 +4,16 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.10
|
||||
|
||||
// Package idna implements IDNA2008 using the compatibility processing
|
||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
||||
// deal with the transition from IDNA2003.
|
||||
//
|
||||
// IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
|
||||
// 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
|
||||
// UTS #46 is defined in http://www.unicode.org/reports/tr46.
|
||||
// See http://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
||||
// UTS #46 is defined in https://www.unicode.org/reports/tr46.
|
||||
// See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
||||
// differences between these two standards.
|
||||
package idna // import "golang.org/x/net/idna"
|
||||
|
||||
@@ -56,23 +58,22 @@ type Option func(*options)
|
||||
// Transitional sets a Profile to use the Transitional mapping as defined in UTS
|
||||
// #46. This will cause, for example, "ß" to be mapped to "ss". Using the
|
||||
// transitional mapping provides a compromise between IDNA2003 and IDNA2008
|
||||
// compatibility. It is used by most browsers when resolving domain names. This
|
||||
// compatibility. It is used by some browsers when resolving domain names. This
|
||||
// option is only meaningful if combined with MapForLookup.
|
||||
func Transitional(transitional bool) Option {
|
||||
return func(o *options) { o.transitional = true }
|
||||
return func(o *options) { o.transitional = transitional }
|
||||
}
|
||||
|
||||
// VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
|
||||
// are longer than allowed by the RFC.
|
||||
//
|
||||
// This option corresponds to the VerifyDnsLength flag in UTS #46.
|
||||
func VerifyDNSLength(verify bool) Option {
|
||||
return func(o *options) { o.verifyDNSLength = verify }
|
||||
}
|
||||
|
||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
||||
//
|
||||
// This is the behavior suggested by the UTS #46 and is adopted by some
|
||||
// browsers.
|
||||
func RemoveLeadingDots(remove bool) Option {
|
||||
return func(o *options) { o.removeLeadingDots = remove }
|
||||
}
|
||||
@@ -80,6 +81,8 @@ func RemoveLeadingDots(remove bool) Option {
|
||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
||||
// In particular, ValidateLabels also sets the CheckHyphens and CheckJoiners flags
|
||||
// in UTS #46.
|
||||
func ValidateLabels(enable bool) Option {
|
||||
return func(o *options) {
|
||||
// Don't override existing mappings, but set one that at least checks
|
||||
@@ -88,25 +91,48 @@ func ValidateLabels(enable bool) Option {
|
||||
o.mapping = normalize
|
||||
}
|
||||
o.trie = trie
|
||||
o.validateLabels = enable
|
||||
o.fromPuny = validateFromPunycode
|
||||
o.checkJoiners = enable
|
||||
o.checkHyphens = enable
|
||||
if enable {
|
||||
o.fromPuny = validateFromPunycode
|
||||
} else {
|
||||
o.fromPuny = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckHyphens sets whether to check for correct use of hyphens ('-') in
|
||||
// labels. Most web browsers do not have this option set, since labels such as
|
||||
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
||||
//
|
||||
// This option corresponds to the CheckHyphens flag in UTS #46.
|
||||
func CheckHyphens(enable bool) Option {
|
||||
return func(o *options) { o.checkHyphens = enable }
|
||||
}
|
||||
|
||||
// CheckJoiners sets whether to check the ContextJ rules as defined in Appendix
|
||||
// A of RFC 5892, concerning the use of joiner runes.
|
||||
//
|
||||
// This option corresponds to the CheckJoiners flag in UTS #46.
|
||||
func CheckJoiners(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.trie = trie
|
||||
o.checkJoiners = enable
|
||||
}
|
||||
}
|
||||
|
||||
// StrictDomainName limits the set of permissible ASCII characters to those
|
||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration.
|
||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration,
|
||||
// but is only useful if ValidateLabels is set.
|
||||
//
|
||||
// This option is useful, for instance, for browsers that allow characters
|
||||
// outside this range, for example a '_' (U+005F LOW LINE). See
|
||||
// http://www.rfc-editor.org/std/std3.txt for more details This option
|
||||
// corresponds to the UseSTD3ASCIIRules option in UTS #46.
|
||||
// http://www.rfc-editor.org/std/std3.txt for more details.
|
||||
//
|
||||
// This option corresponds to the UseSTD3ASCIIRules flag in UTS #46.
|
||||
func StrictDomainName(use bool) Option {
|
||||
return func(o *options) {
|
||||
o.trie = trie
|
||||
o.useSTD3Rules = use
|
||||
o.fromPuny = validateFromPunycode
|
||||
}
|
||||
return func(o *options) { o.useSTD3Rules = use }
|
||||
}
|
||||
|
||||
// NOTE: the following options pull in tables. The tables should not be linked
|
||||
@@ -114,6 +140,8 @@ func StrictDomainName(use bool) Option {
|
||||
|
||||
// BidiRule enables the Bidi rule as defined in RFC 5893. Any application
|
||||
// that relies on proper validation of labels should include this rule.
|
||||
//
|
||||
// This option corresponds to the CheckBidi flag in UTS #46.
|
||||
func BidiRule() Option {
|
||||
return func(o *options) { o.bidirule = bidirule.ValidString }
|
||||
}
|
||||
@@ -149,7 +177,8 @@ func MapForLookup() Option {
|
||||
type options struct {
|
||||
transitional bool
|
||||
useSTD3Rules bool
|
||||
validateLabels bool
|
||||
checkHyphens bool
|
||||
checkJoiners bool
|
||||
verifyDNSLength bool
|
||||
removeLeadingDots bool
|
||||
|
||||
@@ -222,8 +251,11 @@ func (p *Profile) String() string {
|
||||
if p.useSTD3Rules {
|
||||
s += ":UseSTD3Rules"
|
||||
}
|
||||
if p.validateLabels {
|
||||
s += ":ValidateLabels"
|
||||
if p.checkHyphens {
|
||||
s += ":CheckHyphens"
|
||||
}
|
||||
if p.checkJoiners {
|
||||
s += ":CheckJoiners"
|
||||
}
|
||||
if p.verifyDNSLength {
|
||||
s += ":VerifyDNSLength"
|
||||
@@ -251,26 +283,29 @@ var (
|
||||
|
||||
punycode = &Profile{}
|
||||
lookup = &Profile{options{
|
||||
transitional: true,
|
||||
useSTD3Rules: true,
|
||||
validateLabels: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
transitional: transitionalLookup,
|
||||
useSTD3Rules: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
}}
|
||||
display = &Profile{options{
|
||||
useSTD3Rules: true,
|
||||
validateLabels: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
useSTD3Rules: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
}}
|
||||
registration = &Profile{options{
|
||||
useSTD3Rules: true,
|
||||
validateLabels: true,
|
||||
verifyDNSLength: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateRegistration,
|
||||
@@ -297,7 +332,7 @@ func (e runeError) Error() string {
|
||||
}
|
||||
|
||||
// process implements the algorithm described in section 4 of UTS #46,
|
||||
// see http://www.unicode.org/reports/tr46.
|
||||
// see https://www.unicode.org/reports/tr46.
|
||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||
var err error
|
||||
var isBidi bool
|
||||
@@ -337,7 +372,7 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||
}
|
||||
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
||||
labels.set(u)
|
||||
if err == nil && p.validateLabels {
|
||||
if err == nil && p.fromPuny != nil {
|
||||
err = p.fromPuny(p, u)
|
||||
}
|
||||
if err == nil {
|
||||
@@ -678,16 +713,18 @@ func (p *Profile) validateLabel(s string) (err error) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !p.validateLabels {
|
||||
if p.checkHyphens {
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
||||
return &labelError{s, "V2"}
|
||||
}
|
||||
if s[0] == '-' || s[len(s)-1] == '-' {
|
||||
return &labelError{s, "V3"}
|
||||
}
|
||||
}
|
||||
if !p.checkJoiners {
|
||||
return nil
|
||||
}
|
||||
trie := p.trie // p.validateLabels is only set if trie is set.
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
||||
return &labelError{s, "V2"}
|
||||
}
|
||||
if s[0] == '-' || s[len(s)-1] == '-' {
|
||||
return &labelError{s, "V3"}
|
||||
}
|
||||
trie := p.trie // p.checkJoiners is only set if trie is set.
|
||||
// TODO: merge the use of this in the trie.
|
||||
v, sz := trie.lookupString(s)
|
||||
x := info(v)
|
||||
+717
@@ -0,0 +1,717 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.10
|
||||
|
||||
// Package idna implements IDNA2008 using the compatibility processing
|
||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
||||
// deal with the transition from IDNA2003.
|
||||
//
|
||||
// IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
|
||||
// 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
|
||||
// UTS #46 is defined in https://www.unicode.org/reports/tr46.
|
||||
// See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
||||
// differences between these two standards.
|
||||
package idna // import "golang.org/x/net/idna"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/secure/bidirule"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
||||
// evaluated string as lookup.
|
||||
// TODO: the current error handling is, in my opinion, the least opinionated.
|
||||
// Other strategies are also viable, though:
|
||||
// Option 1) Return an empty string in case of error, but allow the user to
|
||||
// specify explicitly which errors to ignore.
|
||||
// Option 2) Return the partially evaluated string if it is itself a valid
|
||||
// string, otherwise return the empty string in case of error.
|
||||
// Option 3) Option 1 and 2.
|
||||
// Option 4) Always return an empty string for now and implement Option 1 as
|
||||
// needed, and document that the return string may not be empty in case of
|
||||
// error in the future.
|
||||
// I think Option 1 is best, but it is quite opinionated.
|
||||
|
||||
// ToASCII is a wrapper for Punycode.ToASCII.
|
||||
func ToASCII(s string) (string, error) {
|
||||
return Punycode.process(s, true)
|
||||
}
|
||||
|
||||
// ToUnicode is a wrapper for Punycode.ToUnicode.
|
||||
func ToUnicode(s string) (string, error) {
|
||||
return Punycode.process(s, false)
|
||||
}
|
||||
|
||||
// An Option configures a Profile at creation time.
|
||||
type Option func(*options)
|
||||
|
||||
// Transitional sets a Profile to use the Transitional mapping as defined in UTS
|
||||
// #46. This will cause, for example, "ß" to be mapped to "ss". Using the
|
||||
// transitional mapping provides a compromise between IDNA2003 and IDNA2008
|
||||
// compatibility. It is used by some browsers when resolving domain names. This
|
||||
// option is only meaningful if combined with MapForLookup.
|
||||
func Transitional(transitional bool) Option {
|
||||
return func(o *options) { o.transitional = transitional }
|
||||
}
|
||||
|
||||
// VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
|
||||
// are longer than allowed by the RFC.
|
||||
//
|
||||
// This option corresponds to the VerifyDnsLength flag in UTS #46.
|
||||
func VerifyDNSLength(verify bool) Option {
|
||||
return func(o *options) { o.verifyDNSLength = verify }
|
||||
}
|
||||
|
||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
||||
func RemoveLeadingDots(remove bool) Option {
|
||||
return func(o *options) { o.removeLeadingDots = remove }
|
||||
}
|
||||
|
||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
||||
// In particular, ValidateLabels also sets the CheckHyphens and CheckJoiners flags
|
||||
// in UTS #46.
|
||||
func ValidateLabels(enable bool) Option {
|
||||
return func(o *options) {
|
||||
// Don't override existing mappings, but set one that at least checks
|
||||
// normalization if it is not set.
|
||||
if o.mapping == nil && enable {
|
||||
o.mapping = normalize
|
||||
}
|
||||
o.trie = trie
|
||||
o.checkJoiners = enable
|
||||
o.checkHyphens = enable
|
||||
if enable {
|
||||
o.fromPuny = validateFromPunycode
|
||||
} else {
|
||||
o.fromPuny = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckHyphens sets whether to check for correct use of hyphens ('-') in
|
||||
// labels. Most web browsers do not have this option set, since labels such as
|
||||
// "r3---sn-apo3qvuoxuxbt-j5pe" are in common use.
|
||||
//
|
||||
// This option corresponds to the CheckHyphens flag in UTS #46.
|
||||
func CheckHyphens(enable bool) Option {
|
||||
return func(o *options) { o.checkHyphens = enable }
|
||||
}
|
||||
|
||||
// CheckJoiners sets whether to check the ContextJ rules as defined in Appendix
|
||||
// A of RFC 5892, concerning the use of joiner runes.
|
||||
//
|
||||
// This option corresponds to the CheckJoiners flag in UTS #46.
|
||||
func CheckJoiners(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.trie = trie
|
||||
o.checkJoiners = enable
|
||||
}
|
||||
}
|
||||
|
||||
// StrictDomainName limits the set of permissible ASCII characters to those
|
||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration,
|
||||
// but is only useful if ValidateLabels is set.
|
||||
//
|
||||
// This option is useful, for instance, for browsers that allow characters
|
||||
// outside this range, for example a '_' (U+005F LOW LINE). See
|
||||
// http://www.rfc-editor.org/std/std3.txt for more details.
|
||||
//
|
||||
// This option corresponds to the UseSTD3ASCIIRules flag in UTS #46.
|
||||
func StrictDomainName(use bool) Option {
|
||||
return func(o *options) { o.useSTD3Rules = use }
|
||||
}
|
||||
|
||||
// NOTE: the following options pull in tables. The tables should not be linked
|
||||
// in as long as the options are not used.
|
||||
|
||||
// BidiRule enables the Bidi rule as defined in RFC 5893. Any application
|
||||
// that relies on proper validation of labels should include this rule.
|
||||
//
|
||||
// This option corresponds to the CheckBidi flag in UTS #46.
|
||||
func BidiRule() Option {
|
||||
return func(o *options) { o.bidirule = bidirule.ValidString }
|
||||
}
|
||||
|
||||
// ValidateForRegistration sets validation options to verify that a given IDN is
|
||||
// properly formatted for registration as defined by Section 4 of RFC 5891.
|
||||
func ValidateForRegistration() Option {
|
||||
return func(o *options) {
|
||||
o.mapping = validateRegistration
|
||||
StrictDomainName(true)(o)
|
||||
ValidateLabels(true)(o)
|
||||
VerifyDNSLength(true)(o)
|
||||
BidiRule()(o)
|
||||
}
|
||||
}
|
||||
|
||||
// MapForLookup sets validation and mapping options such that a given IDN is
|
||||
// transformed for domain name lookup according to the requirements set out in
|
||||
// Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
|
||||
// RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
|
||||
// to add this check.
|
||||
//
|
||||
// The mappings include normalization and mapping case, width and other
|
||||
// compatibility mappings.
|
||||
func MapForLookup() Option {
|
||||
return func(o *options) {
|
||||
o.mapping = validateAndMap
|
||||
StrictDomainName(true)(o)
|
||||
ValidateLabels(true)(o)
|
||||
RemoveLeadingDots(true)(o)
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
transitional bool
|
||||
useSTD3Rules bool
|
||||
checkHyphens bool
|
||||
checkJoiners bool
|
||||
verifyDNSLength bool
|
||||
removeLeadingDots bool
|
||||
|
||||
trie *idnaTrie
|
||||
|
||||
// fromPuny calls validation rules when converting A-labels to U-labels.
|
||||
fromPuny func(p *Profile, s string) error
|
||||
|
||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
||||
mapping func(p *Profile, s string) (string, error)
|
||||
|
||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
||||
// defined in RFC 5893.
|
||||
bidirule func(s string) bool
|
||||
}
|
||||
|
||||
// A Profile defines the configuration of a IDNA mapper.
|
||||
type Profile struct {
|
||||
options
|
||||
}
|
||||
|
||||
func apply(o *options, opts []Option) {
|
||||
for _, f := range opts {
|
||||
f(o)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Profile.
|
||||
//
|
||||
// With no options, the returned Profile is the most permissive and equals the
|
||||
// Punycode Profile. Options can be passed to further restrict the Profile. The
|
||||
// MapForLookup and ValidateForRegistration options set a collection of options,
|
||||
// for lookup and registration purposes respectively, which can be tailored by
|
||||
// adding more fine-grained options, where later options override earlier
|
||||
// options.
|
||||
func New(o ...Option) *Profile {
|
||||
p := &Profile{}
|
||||
apply(&p.options, o)
|
||||
return p
|
||||
}
|
||||
|
||||
// ToASCII converts a domain or domain label to its ASCII form. For example,
|
||||
// ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||
// ToASCII("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToASCII(s string) (string, error) {
|
||||
return p.process(s, true)
|
||||
}
|
||||
|
||||
// ToUnicode converts a domain or domain label to its Unicode form. For example,
|
||||
// ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
|
||||
// ToUnicode("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToUnicode(s string) (string, error) {
|
||||
pp := *p
|
||||
pp.transitional = false
|
||||
return pp.process(s, false)
|
||||
}
|
||||
|
||||
// String reports a string with a description of the profile for debugging
|
||||
// purposes. The string format may change with different versions.
|
||||
func (p *Profile) String() string {
|
||||
s := ""
|
||||
if p.transitional {
|
||||
s = "Transitional"
|
||||
} else {
|
||||
s = "NonTransitional"
|
||||
}
|
||||
if p.useSTD3Rules {
|
||||
s += ":UseSTD3Rules"
|
||||
}
|
||||
if p.checkHyphens {
|
||||
s += ":CheckHyphens"
|
||||
}
|
||||
if p.checkJoiners {
|
||||
s += ":CheckJoiners"
|
||||
}
|
||||
if p.verifyDNSLength {
|
||||
s += ":VerifyDNSLength"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
||||
// of validation.
|
||||
Punycode *Profile = punycode
|
||||
|
||||
// Lookup is the recommended profile for looking up domain names, according
|
||||
// to Section 5 of RFC 5891. The exact configuration of this profile may
|
||||
// change over time.
|
||||
Lookup *Profile = lookup
|
||||
|
||||
// Display is the recommended profile for displaying domain names.
|
||||
// The configuration of this profile may change over time.
|
||||
Display *Profile = display
|
||||
|
||||
// Registration is the recommended profile for checking whether a given
|
||||
// IDN is valid for registration, according to Section 4 of RFC 5891.
|
||||
Registration *Profile = registration
|
||||
|
||||
punycode = &Profile{}
|
||||
lookup = &Profile{options{
|
||||
transitional: true,
|
||||
removeLeadingDots: true,
|
||||
useSTD3Rules: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
}}
|
||||
display = &Profile{options{
|
||||
useSTD3Rules: true,
|
||||
removeLeadingDots: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateAndMap,
|
||||
bidirule: bidirule.ValidString,
|
||||
}}
|
||||
registration = &Profile{options{
|
||||
useSTD3Rules: true,
|
||||
verifyDNSLength: true,
|
||||
checkHyphens: true,
|
||||
checkJoiners: true,
|
||||
trie: trie,
|
||||
fromPuny: validateFromPunycode,
|
||||
mapping: validateRegistration,
|
||||
bidirule: bidirule.ValidString,
|
||||
}}
|
||||
|
||||
// TODO: profiles
|
||||
// Register: recommended for approving domain names: don't do any mappings
|
||||
// but rather reject on invalid input. Bundle or block deviation characters.
|
||||
)
|
||||
|
||||
type labelError struct{ label, code_ string }
|
||||
|
||||
func (e labelError) code() string { return e.code_ }
|
||||
func (e labelError) Error() string {
|
||||
return fmt.Sprintf("idna: invalid label %q", e.label)
|
||||
}
|
||||
|
||||
type runeError rune
|
||||
|
||||
func (e runeError) code() string { return "P1" }
|
||||
func (e runeError) Error() string {
|
||||
return fmt.Sprintf("idna: disallowed rune %U", e)
|
||||
}
|
||||
|
||||
// process implements the algorithm described in section 4 of UTS #46,
|
||||
// see https://www.unicode.org/reports/tr46.
|
||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||
var err error
|
||||
if p.mapping != nil {
|
||||
s, err = p.mapping(p, s)
|
||||
}
|
||||
// Remove leading empty labels.
|
||||
if p.removeLeadingDots {
|
||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
||||
}
|
||||
}
|
||||
// It seems like we should only create this error on ToASCII, but the
|
||||
// UTS 46 conformance tests suggests we should always check this.
|
||||
if err == nil && p.verifyDNSLength && s == "" {
|
||||
err = &labelError{s, "A4"}
|
||||
}
|
||||
labels := labelIter{orig: s}
|
||||
for ; !labels.done(); labels.next() {
|
||||
label := labels.label()
|
||||
if label == "" {
|
||||
// Empty labels are not okay. The label iterator skips the last
|
||||
// label if it is empty.
|
||||
if err == nil && p.verifyDNSLength {
|
||||
err = &labelError{s, "A4"}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(label, acePrefix) {
|
||||
u, err2 := decode(label[len(acePrefix):])
|
||||
if err2 != nil {
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
// Spec says keep the old label.
|
||||
continue
|
||||
}
|
||||
labels.set(u)
|
||||
if err == nil && p.fromPuny != nil {
|
||||
err = p.fromPuny(p, u)
|
||||
}
|
||||
if err == nil {
|
||||
// This should be called on NonTransitional, according to the
|
||||
// spec, but that currently does not have any effect. Use the
|
||||
// original profile to preserve options.
|
||||
err = p.validateLabel(u)
|
||||
}
|
||||
} else if err == nil {
|
||||
err = p.validateLabel(label)
|
||||
}
|
||||
}
|
||||
if toASCII {
|
||||
for labels.reset(); !labels.done(); labels.next() {
|
||||
label := labels.label()
|
||||
if !ascii(label) {
|
||||
a, err2 := encode(acePrefix, label)
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
label = a
|
||||
labels.set(a)
|
||||
}
|
||||
n := len(label)
|
||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) {
|
||||
err = &labelError{label, "A4"}
|
||||
}
|
||||
}
|
||||
}
|
||||
s = labels.result()
|
||||
if toASCII && p.verifyDNSLength && err == nil {
|
||||
// Compute the length of the domain name minus the root label and its dot.
|
||||
n := len(s)
|
||||
if n > 0 && s[n-1] == '.' {
|
||||
n--
|
||||
}
|
||||
if len(s) < 1 || n > 253 {
|
||||
err = &labelError{s, "A4"}
|
||||
}
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
func normalize(p *Profile, s string) (string, error) {
|
||||
return norm.NFC.String(s), nil
|
||||
}
|
||||
|
||||
func validateRegistration(p *Profile, s string) (string, error) {
|
||||
if !norm.NFC.IsNormalString(s) {
|
||||
return s, &labelError{s, "V1"}
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
v, sz := trie.lookupString(s[i:])
|
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) {
|
||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||
// for strict conformance to IDNA2008.
|
||||
case valid, deviation:
|
||||
case disallowed, mapped, unknown, ignored:
|
||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
||||
return s, runeError(r)
|
||||
}
|
||||
i += sz
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func validateAndMap(p *Profile, s string) (string, error) {
|
||||
var (
|
||||
err error
|
||||
b []byte
|
||||
k int
|
||||
)
|
||||
for i := 0; i < len(s); {
|
||||
v, sz := trie.lookupString(s[i:])
|
||||
start := i
|
||||
i += sz
|
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) {
|
||||
case valid:
|
||||
continue
|
||||
case disallowed:
|
||||
if err == nil {
|
||||
r, _ := utf8.DecodeRuneInString(s[start:])
|
||||
err = runeError(r)
|
||||
}
|
||||
continue
|
||||
case mapped, deviation:
|
||||
b = append(b, s[k:start]...)
|
||||
b = info(v).appendMapping(b, s[start:i])
|
||||
case ignored:
|
||||
b = append(b, s[k:start]...)
|
||||
// drop the rune
|
||||
case unknown:
|
||||
b = append(b, s[k:start]...)
|
||||
b = append(b, "\ufffd"...)
|
||||
}
|
||||
k = i
|
||||
}
|
||||
if k == 0 {
|
||||
// No changes so far.
|
||||
s = norm.NFC.String(s)
|
||||
} else {
|
||||
b = append(b, s[k:]...)
|
||||
if norm.NFC.QuickSpan(b) != len(b) {
|
||||
b = norm.NFC.Bytes(b)
|
||||
}
|
||||
// TODO: the punycode converters require strings as input.
|
||||
s = string(b)
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
// A labelIter allows iterating over domain name labels.
|
||||
type labelIter struct {
|
||||
orig string
|
||||
slice []string
|
||||
curStart int
|
||||
curEnd int
|
||||
i int
|
||||
}
|
||||
|
||||
func (l *labelIter) reset() {
|
||||
l.curStart = 0
|
||||
l.curEnd = 0
|
||||
l.i = 0
|
||||
}
|
||||
|
||||
func (l *labelIter) done() bool {
|
||||
return l.curStart >= len(l.orig)
|
||||
}
|
||||
|
||||
func (l *labelIter) result() string {
|
||||
if l.slice != nil {
|
||||
return strings.Join(l.slice, ".")
|
||||
}
|
||||
return l.orig
|
||||
}
|
||||
|
||||
func (l *labelIter) label() string {
|
||||
if l.slice != nil {
|
||||
return l.slice[l.i]
|
||||
}
|
||||
p := strings.IndexByte(l.orig[l.curStart:], '.')
|
||||
l.curEnd = l.curStart + p
|
||||
if p == -1 {
|
||||
l.curEnd = len(l.orig)
|
||||
}
|
||||
return l.orig[l.curStart:l.curEnd]
|
||||
}
|
||||
|
||||
// next sets the value to the next label. It skips the last label if it is empty.
|
||||
func (l *labelIter) next() {
|
||||
l.i++
|
||||
if l.slice != nil {
|
||||
if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" {
|
||||
l.curStart = len(l.orig)
|
||||
}
|
||||
} else {
|
||||
l.curStart = l.curEnd + 1
|
||||
if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' {
|
||||
l.curStart = len(l.orig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *labelIter) set(s string) {
|
||||
if l.slice == nil {
|
||||
l.slice = strings.Split(l.orig, ".")
|
||||
}
|
||||
l.slice[l.i] = s
|
||||
}
|
||||
|
||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||
const acePrefix = "xn--"
|
||||
|
||||
func (p *Profile) simplify(cat category) category {
|
||||
switch cat {
|
||||
case disallowedSTD3Mapped:
|
||||
if p.useSTD3Rules {
|
||||
cat = disallowed
|
||||
} else {
|
||||
cat = mapped
|
||||
}
|
||||
case disallowedSTD3Valid:
|
||||
if p.useSTD3Rules {
|
||||
cat = disallowed
|
||||
} else {
|
||||
cat = valid
|
||||
}
|
||||
case deviation:
|
||||
if !p.transitional {
|
||||
cat = valid
|
||||
}
|
||||
case validNV8, validXV8:
|
||||
// TODO: handle V2008
|
||||
cat = valid
|
||||
}
|
||||
return cat
|
||||
}
|
||||
|
||||
func validateFromPunycode(p *Profile, s string) error {
|
||||
if !norm.NFC.IsNormalString(s) {
|
||||
return &labelError{s, "V1"}
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
v, sz := trie.lookupString(s[i:])
|
||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
||||
return &labelError{s, "V6"}
|
||||
}
|
||||
i += sz
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
zwnj = "\u200c"
|
||||
zwj = "\u200d"
|
||||
)
|
||||
|
||||
type joinState int8
|
||||
|
||||
const (
|
||||
stateStart joinState = iota
|
||||
stateVirama
|
||||
stateBefore
|
||||
stateBeforeVirama
|
||||
stateAfter
|
||||
stateFAIL
|
||||
)
|
||||
|
||||
var joinStates = [][numJoinTypes]joinState{
|
||||
stateStart: {
|
||||
joiningL: stateBefore,
|
||||
joiningD: stateBefore,
|
||||
joinZWNJ: stateFAIL,
|
||||
joinZWJ: stateFAIL,
|
||||
joinVirama: stateVirama,
|
||||
},
|
||||
stateVirama: {
|
||||
joiningL: stateBefore,
|
||||
joiningD: stateBefore,
|
||||
},
|
||||
stateBefore: {
|
||||
joiningL: stateBefore,
|
||||
joiningD: stateBefore,
|
||||
joiningT: stateBefore,
|
||||
joinZWNJ: stateAfter,
|
||||
joinZWJ: stateFAIL,
|
||||
joinVirama: stateBeforeVirama,
|
||||
},
|
||||
stateBeforeVirama: {
|
||||
joiningL: stateBefore,
|
||||
joiningD: stateBefore,
|
||||
joiningT: stateBefore,
|
||||
},
|
||||
stateAfter: {
|
||||
joiningL: stateFAIL,
|
||||
joiningD: stateBefore,
|
||||
joiningT: stateAfter,
|
||||
joiningR: stateStart,
|
||||
joinZWNJ: stateFAIL,
|
||||
joinZWJ: stateFAIL,
|
||||
joinVirama: stateAfter, // no-op as we can't accept joiners here
|
||||
},
|
||||
stateFAIL: {
|
||||
0: stateFAIL,
|
||||
joiningL: stateFAIL,
|
||||
joiningD: stateFAIL,
|
||||
joiningT: stateFAIL,
|
||||
joiningR: stateFAIL,
|
||||
joinZWNJ: stateFAIL,
|
||||
joinZWJ: stateFAIL,
|
||||
joinVirama: stateFAIL,
|
||||
},
|
||||
}
|
||||
|
||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||
// already implicitly satisfied by the overall implementation.
|
||||
func (p *Profile) validateLabel(s string) error {
|
||||
if s == "" {
|
||||
if p.verifyDNSLength {
|
||||
return &labelError{s, "A4"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if p.bidirule != nil && !p.bidirule(s) {
|
||||
return &labelError{s, "B"}
|
||||
}
|
||||
if p.checkHyphens {
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' {
|
||||
return &labelError{s, "V2"}
|
||||
}
|
||||
if s[0] == '-' || s[len(s)-1] == '-' {
|
||||
return &labelError{s, "V3"}
|
||||
}
|
||||
}
|
||||
if !p.checkJoiners {
|
||||
return nil
|
||||
}
|
||||
trie := p.trie // p.checkJoiners is only set if trie is set.
|
||||
// TODO: merge the use of this in the trie.
|
||||
v, sz := trie.lookupString(s)
|
||||
x := info(v)
|
||||
if x.isModifier() {
|
||||
return &labelError{s, "V5"}
|
||||
}
|
||||
// Quickly return in the absence of zero-width (non) joiners.
|
||||
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 {
|
||||
return nil
|
||||
}
|
||||
st := stateStart
|
||||
for i := 0; ; {
|
||||
jt := x.joinType()
|
||||
if s[i:i+sz] == zwj {
|
||||
jt = joinZWJ
|
||||
} else if s[i:i+sz] == zwnj {
|
||||
jt = joinZWNJ
|
||||
}
|
||||
st = joinStates[st][jt]
|
||||
if x.isViramaModifier() {
|
||||
st = joinStates[st][joinVirama]
|
||||
}
|
||||
if i += sz; i == len(s) {
|
||||
break
|
||||
}
|
||||
v, sz = trie.lookupString(s[i:])
|
||||
x = info(v)
|
||||
}
|
||||
if st == stateFAIL || st == stateAfter {
|
||||
return &labelError{s, "C"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ascii(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.18
|
||||
|
||||
package idna
|
||||
|
||||
const transitionalLookup = true
|
||||
+25
-11
@@ -49,6 +49,7 @@ func decode(encoded string) (string, error) {
|
||||
}
|
||||
}
|
||||
i, n, bias := int32(0), initialN, initialBias
|
||||
overflow := false
|
||||
for pos < len(encoded) {
|
||||
oldI, w := i, int32(1)
|
||||
for k := base; ; k += base {
|
||||
@@ -60,29 +61,32 @@ func decode(encoded string) (string, error) {
|
||||
return "", punyError(encoded)
|
||||
}
|
||||
pos++
|
||||
i += digit * w
|
||||
if i < 0 {
|
||||
i, overflow = madd(i, digit, w)
|
||||
if overflow {
|
||||
return "", punyError(encoded)
|
||||
}
|
||||
t := k - bias
|
||||
if t < tmin {
|
||||
if k <= bias {
|
||||
t = tmin
|
||||
} else if t > tmax {
|
||||
} else if k >= bias+tmax {
|
||||
t = tmax
|
||||
}
|
||||
if digit < t {
|
||||
break
|
||||
}
|
||||
w *= base - t
|
||||
if w >= math.MaxInt32/base {
|
||||
w, overflow = madd(0, w, base-t)
|
||||
if overflow {
|
||||
return "", punyError(encoded)
|
||||
}
|
||||
}
|
||||
if len(output) >= 1024 {
|
||||
return "", punyError(encoded)
|
||||
}
|
||||
x := int32(len(output) + 1)
|
||||
bias = adapt(i-oldI, x, oldI == 0)
|
||||
n += i / x
|
||||
i %= x
|
||||
if n > utf8.MaxRune || len(output) >= 1024 {
|
||||
if n < 0 || n > utf8.MaxRune {
|
||||
return "", punyError(encoded)
|
||||
}
|
||||
output = append(output, 0)
|
||||
@@ -115,6 +119,7 @@ func encode(prefix, s string) (string, error) {
|
||||
if b > 0 {
|
||||
output = append(output, '-')
|
||||
}
|
||||
overflow := false
|
||||
for remaining != 0 {
|
||||
m := int32(0x7fffffff)
|
||||
for _, r := range s {
|
||||
@@ -122,8 +127,8 @@ func encode(prefix, s string) (string, error) {
|
||||
m = r
|
||||
}
|
||||
}
|
||||
delta += (m - n) * (h + 1)
|
||||
if delta < 0 {
|
||||
delta, overflow = madd(delta, m-n, h+1)
|
||||
if overflow {
|
||||
return "", punyError(s)
|
||||
}
|
||||
n = m
|
||||
@@ -141,9 +146,9 @@ func encode(prefix, s string) (string, error) {
|
||||
q := delta
|
||||
for k := base; ; k += base {
|
||||
t := k - bias
|
||||
if t < tmin {
|
||||
if k <= bias {
|
||||
t = tmin
|
||||
} else if t > tmax {
|
||||
} else if k >= bias+tmax {
|
||||
t = tmax
|
||||
}
|
||||
if q < t {
|
||||
@@ -164,6 +169,15 @@ func encode(prefix, s string) (string, error) {
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// madd computes a + (b * c), detecting overflow.
|
||||
func madd(a, b, c int32) (next int32, overflow bool) {
|
||||
p := int64(b) * int64(c)
|
||||
if p > math.MaxInt32-int64(a) {
|
||||
return 0, true
|
||||
}
|
||||
return a + int32(p), false
|
||||
}
|
||||
|
||||
func decodeDigit(x byte) (digit int32, ok bool) {
|
||||
switch {
|
||||
case '0' <= x && x <= '9':
|
||||
|
||||
Generated
Vendored
+4
-2
@@ -1,11 +1,13 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
//go:build go1.10 && !go1.13
|
||||
|
||||
package idna
|
||||
|
||||
// UnicodeVersion is the Unicode version from which the tables in this package are derived.
|
||||
const UnicodeVersion = "10.0.0"
|
||||
|
||||
var mappings string = "" + // Size: 8176 bytes
|
||||
var mappings string = "" + // Size: 8175 bytes
|
||||
"\x00\x01 \x03 ̈\x01a\x03 ̄\x012\x013\x03 ́\x03 ̧\x011\x01o\x051⁄4\x051⁄2" +
|
||||
"\x053⁄4\x03i̇\x03l·\x03ʼn\x01s\x03dž\x03ⱥ\x03ⱦ\x01h\x01j\x01r\x01w\x01y" +
|
||||
"\x03 ̆\x03 ̇\x03 ̊\x03 ̨\x03 ̃\x03 ̋\x01l\x01x\x04̈́\x03 ι\x01;\x05 ̈́" +
|
||||
@@ -4554,4 +4556,4 @@ var idnaSparseValues = [1915]valueRange{
|
||||
{value: 0x0040, lo: 0xb0, hi: 0xbf},
|
||||
}
|
||||
|
||||
// Total table size 42115 bytes (41KiB); checksum: F4A1FA4E
|
||||
// Total table size 42114 bytes (41KiB); checksum: 355A58A4
|
||||
+4653
File diff suppressed because it is too large
Load Diff
+4733
File diff suppressed because it is too large
Load Diff
+4959
File diff suppressed because it is too large
Load Diff
+5144
File diff suppressed because it is too large
Load Diff
+4486
File diff suppressed because it is too large
Load Diff
-21
@@ -6,27 +6,6 @@
|
||||
|
||||
package idna
|
||||
|
||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
||||
func (c info) appendMapping(b []byte, s string) []byte {
|
||||
index := int(c >> indexShift)
|
||||
if c&xorBit == 0 {
|
||||
s := mappings[index:]
|
||||
return append(b, s[1:s[0]+1]...)
|
||||
}
|
||||
b = append(b, s...)
|
||||
if c&inlineXOR == inlineXOR {
|
||||
// TODO: support and handle two-byte inline masks
|
||||
b[len(b)-1] ^= byte(index)
|
||||
} else {
|
||||
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
||||
index++
|
||||
b[p] ^= xorData[index]
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Sparse block handling code.
|
||||
|
||||
type valueRange struct {
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.16
|
||||
|
||||
package idna
|
||||
|
||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
||||
func (c info) appendMapping(b []byte, s string) []byte {
|
||||
index := int(c >> indexShift)
|
||||
if c&xorBit == 0 {
|
||||
s := mappings[index:]
|
||||
return append(b, s[1:s[0]+1]...)
|
||||
}
|
||||
b = append(b, s...)
|
||||
if c&inlineXOR == inlineXOR {
|
||||
// TODO: support and handle two-byte inline masks
|
||||
b[len(b)-1] ^= byte(index)
|
||||
} else {
|
||||
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
||||
index++
|
||||
b[p] ^= xorData[index]
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.16
|
||||
|
||||
package idna
|
||||
|
||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
||||
func (c info) appendMapping(b []byte, s string) []byte {
|
||||
index := int(c >> indexShift)
|
||||
if c&xorBit == 0 {
|
||||
p := index
|
||||
return append(b, mappings[mappingIndex[p]:mappingIndex[p+1]]...)
|
||||
}
|
||||
b = append(b, s...)
|
||||
if c&inlineXOR == inlineXOR {
|
||||
// TODO: support and handle two-byte inline masks
|
||||
b[len(b)-1] ^= byte(index)
|
||||
} else {
|
||||
for p := len(b) - int(xorData[index]); p < len(b); p++ {
|
||||
index++
|
||||
b[p] ^= xorData[index]
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
+17
-17
@@ -17,23 +17,23 @@ package idna
|
||||
//
|
||||
// The per-rune values have the following format:
|
||||
//
|
||||
// if mapped {
|
||||
// if inlinedXOR {
|
||||
// 15..13 inline XOR marker
|
||||
// 12..11 unused
|
||||
// 10..3 inline XOR mask
|
||||
// } else {
|
||||
// 15..3 index into xor or mapping table
|
||||
// }
|
||||
// } else {
|
||||
// 15..14 unused
|
||||
// 13 mayNeedNorm
|
||||
// 12..11 attributes
|
||||
// 10..8 joining type
|
||||
// 7..3 category type
|
||||
// }
|
||||
// 2 use xor pattern
|
||||
// 1..0 mapped category
|
||||
// if mapped {
|
||||
// if inlinedXOR {
|
||||
// 15..13 inline XOR marker
|
||||
// 12..11 unused
|
||||
// 10..3 inline XOR mask
|
||||
// } else {
|
||||
// 15..3 index into xor or mapping table
|
||||
// }
|
||||
// } else {
|
||||
// 15..14 unused
|
||||
// 13 mayNeedNorm
|
||||
// 12..11 attributes
|
||||
// 10..8 joining type
|
||||
// 7..3 category type
|
||||
// }
|
||||
// 2 use xor pattern
|
||||
// 1..0 mapped category
|
||||
//
|
||||
// See the definitions below for a more detailed description of the various
|
||||
// bits.
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package httpcommon
|
||||
|
||||
import "strings"
|
||||
|
||||
// The HTTP protocols are defined in terms of ASCII, not Unicode. This file
|
||||
// contains helper functions which may use Unicode-aware functions which would
|
||||
// otherwise be unsafe and could introduce vulnerabilities if used improperly.
|
||||
|
||||
// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
|
||||
// are equal, ASCII-case-insensitively.
|
||||
func asciiEqualFold(s, t string) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if lower(s[i]) != lower(t[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lower returns the ASCII lowercase version of b.
|
||||
func lower(b byte) byte {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
return b + ('a' - 'A')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// isASCIIPrint returns whether s is ASCII and printable according to
|
||||
// https://tools.ietf.org/html/rfc20#section-4.2.
|
||||
func isASCIIPrint(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < ' ' || s[i] > '~' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiToLower returns the lowercase version of s if s is ASCII and printable,
|
||||
// and whether or not it was.
|
||||
func asciiToLower(s string) (lower string, ok bool) {
|
||||
if !isASCIIPrint(s) {
|
||||
return "", false
|
||||
}
|
||||
return strings.ToLower(s), true
|
||||
}
|
||||
Generated
Vendored
+34
-7
@@ -1,12 +1,11 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
package httpcommon
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"net/textproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -28,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",
|
||||
@@ -54,6 +60,7 @@ func buildCommonHeaderMaps() {
|
||||
"link",
|
||||
"location",
|
||||
"max-forwards",
|
||||
"origin",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"range",
|
||||
@@ -69,20 +76,40 @@ 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))
|
||||
for _, v := range common {
|
||||
chk := http.CanonicalHeaderKey(v)
|
||||
chk := textproto.CanonicalMIMEHeaderKey(v)
|
||||
commonLowerHeader[chk] = v
|
||||
commonCanonHeader[v] = chk
|
||||
}
|
||||
}
|
||||
|
||||
func lowerHeader(v string) string {
|
||||
// LowerHeader returns the lowercase form of a header name,
|
||||
// used on the wire for HTTP/2 and HTTP/3 requests.
|
||||
func LowerHeader(v string) (lower string, ascii bool) {
|
||||
buildCommonHeaderMapsOnce()
|
||||
if s, ok := commonLowerHeader[v]; ok {
|
||||
return s, true
|
||||
}
|
||||
return asciiToLower(v)
|
||||
}
|
||||
|
||||
// CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".)
|
||||
func CanonicalHeader(v string) string {
|
||||
buildCommonHeaderMapsOnce()
|
||||
if s, ok := commonCanonHeader[v]; ok {
|
||||
return s
|
||||
}
|
||||
return strings.ToLower(v)
|
||||
return textproto.CanonicalMIMEHeaderKey(v)
|
||||
}
|
||||
|
||||
// CachedCanonicalHeader returns the canonical form of a well-known header name.
|
||||
func CachedCanonicalHeader(v string) (string, bool) {
|
||||
buildCommonHeaderMapsOnce()
|
||||
s, ok := commonCanonHeader[v]
|
||||
return s, ok
|
||||
}
|
||||
+467
@@ -0,0 +1,467 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package httpcommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
|
||||
)
|
||||
|
||||
// Request is a subset of http.Request.
|
||||
// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
|
||||
// without creating a dependency cycle.
|
||||
type Request struct {
|
||||
URL *url.URL
|
||||
Method string
|
||||
Host string
|
||||
Header map[string][]string
|
||||
Trailer map[string][]string
|
||||
ActualContentLength int64 // 0 means 0, -1 means unknown
|
||||
}
|
||||
|
||||
// EncodeHeadersParam is parameters to EncodeHeaders.
|
||||
type EncodeHeadersParam struct {
|
||||
Request Request
|
||||
|
||||
// AddGzipHeader indicates that an "accept-encoding: gzip" header should be
|
||||
// added to the request.
|
||||
AddGzipHeader bool
|
||||
|
||||
// PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting.
|
||||
PeerMaxHeaderListSize uint64
|
||||
|
||||
// DefaultUserAgent is the User-Agent header to send when the request
|
||||
// neither contains a User-Agent nor disables it.
|
||||
DefaultUserAgent string
|
||||
}
|
||||
|
||||
// EncodeHeadersResult is the result of EncodeHeaders.
|
||||
type EncodeHeadersResult struct {
|
||||
HasBody bool
|
||||
HasTrailers bool
|
||||
}
|
||||
|
||||
// EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3.
|
||||
// It validates a request and calls headerf with each pseudo-header and header
|
||||
// for the request.
|
||||
// The headerf function is called with the validated, canonicalized header name.
|
||||
func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
|
||||
req := param.Request
|
||||
|
||||
// Check for invalid connection-level headers.
|
||||
if err := checkConnHeaders(req.Header); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if req.URL == nil {
|
||||
return res, errors.New("Request.URL is nil")
|
||||
}
|
||||
|
||||
host := req.Host
|
||||
if host == "" {
|
||||
host = req.URL.Host
|
||||
}
|
||||
host, err := httpguts.PunycodeHostPort(host)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if !httpguts.ValidHostHeader(host) {
|
||||
return res, errors.New("invalid Host header")
|
||||
}
|
||||
|
||||
// isNormalConnect is true if this is a non-extended CONNECT request.
|
||||
isNormalConnect := false
|
||||
var protocol string
|
||||
if vv := req.Header[":protocol"]; len(vv) > 0 {
|
||||
protocol = vv[0]
|
||||
}
|
||||
if req.Method == "CONNECT" && protocol == "" {
|
||||
isNormalConnect = true
|
||||
} else if protocol != "" && req.Method != "CONNECT" {
|
||||
return res, errors.New("invalid :protocol header in non-CONNECT request")
|
||||
}
|
||||
|
||||
// Validate the path, except for non-extended CONNECT requests which have no path.
|
||||
var path string
|
||||
if !isNormalConnect {
|
||||
path = req.URL.RequestURI()
|
||||
if !validPseudoPath(path) {
|
||||
orig := path
|
||||
path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
|
||||
if !validPseudoPath(path) {
|
||||
if req.URL.Opaque != "" {
|
||||
return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
|
||||
} else {
|
||||
return res, fmt.Errorf("invalid request :path %q", orig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any invalid headers+trailers and return an error before we
|
||||
// potentially pollute our hpack state. (We want to be able to
|
||||
// continue to reuse the hpack encoder for future requests)
|
||||
if err := validateHeaders(req.Header); err != "" {
|
||||
return res, fmt.Errorf("invalid HTTP header %s", err)
|
||||
}
|
||||
if err := validateHeaders(req.Trailer); err != "" {
|
||||
return res, fmt.Errorf("invalid HTTP trailer %s", err)
|
||||
}
|
||||
|
||||
trailers, err := commaSeparatedTrailers(req.Trailer)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
enumerateHeaders := func(f func(name, value string)) {
|
||||
// 8.1.2.3 Request Pseudo-Header Fields
|
||||
// The :path pseudo-header field includes the path and query parts of the
|
||||
// target URI (the path-absolute production and optionally a '?' character
|
||||
// followed by the query production, see Sections 3.3 and 3.4 of
|
||||
// [RFC3986]).
|
||||
f(":authority", host)
|
||||
m := req.Method
|
||||
if m == "" {
|
||||
m = "GET"
|
||||
}
|
||||
f(":method", m)
|
||||
if !isNormalConnect {
|
||||
f(":path", path)
|
||||
f(":scheme", req.URL.Scheme)
|
||||
}
|
||||
if protocol != "" {
|
||||
f(":protocol", protocol)
|
||||
}
|
||||
if trailers != "" {
|
||||
f("trailer", trailers)
|
||||
}
|
||||
|
||||
var didUA bool
|
||||
for k, vv := range req.Header {
|
||||
if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
|
||||
// Host is :authority, already sent.
|
||||
// Content-Length is automatic, set below.
|
||||
continue
|
||||
} else if asciiEqualFold(k, "connection") ||
|
||||
asciiEqualFold(k, "proxy-connection") ||
|
||||
asciiEqualFold(k, "transfer-encoding") ||
|
||||
asciiEqualFold(k, "upgrade") ||
|
||||
asciiEqualFold(k, "keep-alive") {
|
||||
// Per 8.1.2.2 Connection-Specific Header
|
||||
// Fields, don't send connection-specific
|
||||
// fields. We have already checked if any
|
||||
// are error-worthy so just ignore the rest.
|
||||
continue
|
||||
} else if asciiEqualFold(k, "user-agent") {
|
||||
// Match Go's http1 behavior: at most one
|
||||
// User-Agent. If set to nil or empty string,
|
||||
// then omit it. Otherwise if not mentioned,
|
||||
// include the default (below).
|
||||
didUA = true
|
||||
if len(vv) < 1 {
|
||||
continue
|
||||
}
|
||||
vv = vv[:1]
|
||||
if vv[0] == "" {
|
||||
continue
|
||||
}
|
||||
} else if asciiEqualFold(k, "cookie") {
|
||||
// Per 8.1.2.5 To allow for better compression efficiency, the
|
||||
// Cookie header field MAY be split into separate header fields,
|
||||
// each with one or more cookie-pairs.
|
||||
for _, v := range vv {
|
||||
for {
|
||||
p := strings.IndexByte(v, ';')
|
||||
if p < 0 {
|
||||
break
|
||||
}
|
||||
f("cookie", v[:p])
|
||||
p++
|
||||
// strip space after semicolon if any.
|
||||
for p+1 <= len(v) && v[p] == ' ' {
|
||||
p++
|
||||
}
|
||||
v = v[p:]
|
||||
}
|
||||
if len(v) > 0 {
|
||||
f("cookie", v)
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if k == ":protocol" {
|
||||
// :protocol pseudo-header was already sent above.
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range vv {
|
||||
f(k, v)
|
||||
}
|
||||
}
|
||||
if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
|
||||
f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
|
||||
}
|
||||
if param.AddGzipHeader {
|
||||
f("accept-encoding", "gzip")
|
||||
}
|
||||
if !didUA {
|
||||
f("user-agent", param.DefaultUserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// Do a first pass over the headers counting bytes to ensure
|
||||
// we don't exceed cc.peerMaxHeaderListSize. This is done as a
|
||||
// separate pass before encoding the headers to prevent
|
||||
// modifying the hpack state.
|
||||
if param.PeerMaxHeaderListSize > 0 {
|
||||
hlSize := uint64(0)
|
||||
enumerateHeaders(func(name, value string) {
|
||||
hf := hpack.HeaderField{Name: name, Value: value}
|
||||
hlSize += uint64(hf.Size())
|
||||
})
|
||||
|
||||
if hlSize > param.PeerMaxHeaderListSize {
|
||||
return res, ErrRequestHeaderListSize
|
||||
}
|
||||
}
|
||||
|
||||
trace := httptrace.ContextClientTrace(ctx)
|
||||
|
||||
// Header list size is ok. Write the headers.
|
||||
enumerateHeaders(func(name, value string) {
|
||||
name, ascii := LowerHeader(name)
|
||||
if !ascii {
|
||||
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
|
||||
// field names have to be ASCII characters (just as in HTTP/1.x).
|
||||
return
|
||||
}
|
||||
|
||||
headerf(name, value)
|
||||
|
||||
if trace != nil && trace.WroteHeaderField != nil {
|
||||
trace.WroteHeaderField(name, []string{value})
|
||||
}
|
||||
})
|
||||
|
||||
res.HasBody = req.ActualContentLength != 0
|
||||
res.HasTrailers = trailers != ""
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
|
||||
// for a request.
|
||||
func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
|
||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||
if !disableCompression &&
|
||||
len(header["Accept-Encoding"]) == 0 &&
|
||||
len(header["Range"]) == 0 &&
|
||||
method != "HEAD" {
|
||||
// Request gzip only, not deflate. Deflate is ambiguous and
|
||||
// not as universally supported anyway.
|
||||
// See: https://zlib.net/zlib_faq.html#faq39
|
||||
//
|
||||
// Note that we don't request this for HEAD requests,
|
||||
// due to a bug in nginx:
|
||||
// http://trac.nginx.org/nginx/ticket/358
|
||||
// https://golang.org/issue/5522
|
||||
//
|
||||
// We don't request gzip if the request is for a range, since
|
||||
// auto-decoding a portion of a gzipped document will just fail
|
||||
// anyway. See https://golang.org/issue/8923
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkConnHeaders checks whether req has any invalid connection-level headers.
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3
|
||||
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1
|
||||
//
|
||||
// Certain headers are special-cased as okay but not transmitted later.
|
||||
// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
|
||||
func checkConnHeaders(h map[string][]string) error {
|
||||
if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
|
||||
return fmt.Errorf("invalid Upgrade request header: %q", vv)
|
||||
}
|
||||
if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
|
||||
return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
|
||||
}
|
||||
if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
|
||||
return fmt.Errorf("invalid Connection request header: %q", vv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
|
||||
keys := make([]string, 0, len(trailer))
|
||||
for k := range trailer {
|
||||
k = CanonicalHeader(k)
|
||||
switch k {
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
return "", fmt.Errorf("invalid Trailer key %q", k)
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
return strings.Join(keys, ","), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// validPseudoPath reports whether v is a valid :path pseudo-header
|
||||
// value. It must be either:
|
||||
//
|
||||
// - a non-empty string starting with '/'
|
||||
// - the string '*', for OPTIONS requests.
|
||||
//
|
||||
// For now this is only used a quick check for deciding when to clean
|
||||
// up Opaque URLs before sending requests from the Transport.
|
||||
// See golang.org/issue/16847
|
||||
//
|
||||
// We used to enforce that the path also didn't start with "//", but
|
||||
// Google's GFE accepts such paths and Chrome sends them, so ignore
|
||||
// that part of the spec. See golang.org/issue/19103.
|
||||
func validPseudoPath(v string) bool {
|
||||
return (len(v) > 0 && v[0] == '/') || v == "*"
|
||||
}
|
||||
|
||||
func validateHeaders(hdrs map[string][]string) string {
|
||||
for k, vv := range hdrs {
|
||||
if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
|
||||
return fmt.Sprintf("name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
// Don't include the value in the error,
|
||||
// because it may be sensitive.
|
||||
return fmt.Sprintf("value for header %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// shouldSendReqContentLength reports whether we should send
|
||||
// a "content-length" request header. This logic is basically a copy of the net/http
|
||||
// transferWriter.shouldSendContentLength.
|
||||
// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
|
||||
// -1 means unknown.
|
||||
func shouldSendReqContentLength(method string, contentLength int64) bool {
|
||||
if contentLength > 0 {
|
||||
return true
|
||||
}
|
||||
if contentLength < 0 {
|
||||
return false
|
||||
}
|
||||
// For zero bodies, whether we send a content-length depends on the method.
|
||||
// It also kinda doesn't matter for http2 either way, with END_STREAM.
|
||||
switch method {
|
||||
case "POST", "PUT", "PATCH":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ServerRequestParam is parameters to NewServerRequest.
|
||||
type ServerRequestParam struct {
|
||||
Method string
|
||||
Scheme, Authority, Path string
|
||||
Protocol string
|
||||
Header map[string][]string
|
||||
}
|
||||
|
||||
// ServerRequestResult is the result of NewServerRequest.
|
||||
type ServerRequestResult struct {
|
||||
// Various http.Request fields.
|
||||
URL *url.URL
|
||||
RequestURI string
|
||||
Trailer map[string][]string
|
||||
|
||||
NeedsContinue bool // client provided an "Expect: 100-continue" header
|
||||
|
||||
// If the request should be rejected, this is a short string suitable for passing
|
||||
// to the http2 package's CountError function.
|
||||
// It might be a bit odd to return errors this way rather than returning an error,
|
||||
// but this ensures we don't forget to include a CountError reason.
|
||||
InvalidReason string
|
||||
}
|
||||
|
||||
func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
|
||||
needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
|
||||
if needsContinue {
|
||||
delete(rp.Header, "Expect")
|
||||
}
|
||||
// Merge Cookie headers into one "; "-delimited value.
|
||||
if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
|
||||
rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
|
||||
}
|
||||
|
||||
// Setup Trailers
|
||||
var trailer map[string][]string
|
||||
for _, v := range rp.Header["Trailer"] {
|
||||
for _, key := range strings.Split(v, ",") {
|
||||
key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
|
||||
switch key {
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
// Bogus. (copy of http1 rules)
|
||||
// Ignore.
|
||||
default:
|
||||
if trailer == nil {
|
||||
trailer = make(map[string][]string)
|
||||
}
|
||||
trailer[key] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(rp.Header, "Trailer")
|
||||
|
||||
// "':authority' MUST NOT include the deprecated userinfo subcomponent
|
||||
// for "http" or "https" schemed URIs."
|
||||
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
|
||||
if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
|
||||
return ServerRequestResult{
|
||||
InvalidReason: "userinfo_in_authority",
|
||||
}
|
||||
}
|
||||
|
||||
var url_ *url.URL
|
||||
var requestURI string
|
||||
if rp.Method == "CONNECT" && rp.Protocol == "" {
|
||||
url_ = &url.URL{Host: rp.Authority}
|
||||
requestURI = rp.Authority // mimic HTTP/1 server behavior
|
||||
} else {
|
||||
var err error
|
||||
url_, err = url.ParseRequestURI(rp.Path)
|
||||
if err != nil {
|
||||
return ServerRequestResult{
|
||||
InvalidReason: "bad_path",
|
||||
}
|
||||
}
|
||||
requestURI = rp.Path
|
||||
}
|
||||
|
||||
return ServerRequestResult{
|
||||
URL: url_,
|
||||
NeedsContinue: needsContinue,
|
||||
RequestURI: requestURI,
|
||||
Trailer: trailer,
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.10
|
||||
//go:build go1.10
|
||||
|
||||
package bidirule
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.10
|
||||
//go:build !go1.10
|
||||
|
||||
package bidirule
|
||||
|
||||
|
||||
+8
-4
@@ -78,8 +78,8 @@ type SpanningTransformer interface {
|
||||
// considering the error err.
|
||||
//
|
||||
// A nil error means that all input bytes are known to be identical to the
|
||||
// output produced by the Transformer. A nil error can be be returned
|
||||
// regardless of whether atEOF is true. If err is nil, then then n must
|
||||
// output produced by the Transformer. A nil error can be returned
|
||||
// regardless of whether atEOF is true. If err is nil, then n must
|
||||
// equal len(src); the converse is not necessarily true.
|
||||
//
|
||||
// ErrEndOfSpan means that the Transformer output may differ from the
|
||||
@@ -493,7 +493,7 @@ func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err erro
|
||||
return dstL.n, srcL.p, err
|
||||
}
|
||||
|
||||
// Deprecated: use runes.Remove instead.
|
||||
// Deprecated: Use runes.Remove instead.
|
||||
func RemoveFunc(f func(r rune) bool) Transformer {
|
||||
return removeF(f)
|
||||
}
|
||||
@@ -648,7 +648,8 @@ func String(t Transformer, s string) (result string, n int, err error) {
|
||||
// Transform the remaining input, growing dst and src buffers as necessary.
|
||||
for {
|
||||
n := copy(src, s[pSrc:])
|
||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], pSrc+n == len(s))
|
||||
atEOF := pSrc+n == len(s)
|
||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], atEOF)
|
||||
pDst += nDst
|
||||
pSrc += nSrc
|
||||
|
||||
@@ -659,6 +660,9 @@ func String(t Transformer, s string) (result string, n int, err error) {
|
||||
dst = grow(dst, pDst)
|
||||
}
|
||||
} else if err == ErrShortSrc {
|
||||
if atEOF {
|
||||
return string(dst[:pDst]), pSrc, err
|
||||
}
|
||||
if nSrc == 0 {
|
||||
src = grow(src, 0)
|
||||
}
|
||||
|
||||
+192
-31
@@ -6,21 +6,20 @@
|
||||
|
||||
// Package bidi contains functionality for bidirectional text support.
|
||||
//
|
||||
// See http://www.unicode.org/reports/tr9.
|
||||
// See https://www.unicode.org/reports/tr9.
|
||||
//
|
||||
// NOTE: UNDER CONSTRUCTION. This API may change in backwards incompatible ways
|
||||
// and without notice.
|
||||
package bidi // import "golang.org/x/text/unicode/bidi"
|
||||
|
||||
// TODO:
|
||||
// The following functionality would not be hard to implement, but hinges on
|
||||
// the definition of a Segmenter interface. For now this is up to the user.
|
||||
// - Iterate over paragraphs
|
||||
// - Segmenter to iterate over runs directly from a given text.
|
||||
// Also:
|
||||
// TODO
|
||||
// - Transformer for reordering?
|
||||
// - Transformer (validator, really) for Bidi Rule.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// This API tries to avoid dealing with embedding levels for now. Under the hood
|
||||
// these will be computed, but the question is to which extent the user should
|
||||
// know they exist. We should at some point allow the user to specify an
|
||||
@@ -49,7 +48,9 @@ const (
|
||||
Neutral
|
||||
)
|
||||
|
||||
type options struct{}
|
||||
type options struct {
|
||||
defaultDirection Direction
|
||||
}
|
||||
|
||||
// An Option is an option for Bidi processing.
|
||||
type Option func(*options)
|
||||
@@ -66,12 +67,62 @@ type Option func(*options)
|
||||
// DefaultDirection sets the default direction for a Paragraph. The direction is
|
||||
// overridden if the text contains directional characters.
|
||||
func DefaultDirection(d Direction) Option {
|
||||
panic("unimplemented")
|
||||
return func(opts *options) {
|
||||
opts.defaultDirection = d
|
||||
}
|
||||
}
|
||||
|
||||
// A Paragraph holds a single Paragraph for Bidi processing.
|
||||
type Paragraph struct {
|
||||
// buffers
|
||||
p []byte
|
||||
o Ordering
|
||||
opts []Option
|
||||
types []Class
|
||||
pairTypes []bracketType
|
||||
pairValues []rune
|
||||
runes []rune
|
||||
options options
|
||||
}
|
||||
|
||||
// Initialize the p.pairTypes, p.pairValues and p.types from the input previously
|
||||
// set by p.SetBytes() or p.SetString(). Also limit the input up to (and including) a paragraph
|
||||
// separator (bidi class B).
|
||||
//
|
||||
// The function p.Order() needs these values to be set, so this preparation could be postponed.
|
||||
// But since the SetBytes and SetStrings functions return the length of the input up to the paragraph
|
||||
// separator, the whole input needs to be processed anyway and should not be done twice.
|
||||
//
|
||||
// The function has the same return values as SetBytes() / SetString()
|
||||
func (p *Paragraph) prepareInput() (n int, err error) {
|
||||
p.runes = bytes.Runes(p.p)
|
||||
bytecount := 0
|
||||
// clear slices from previous SetString or SetBytes
|
||||
p.pairTypes = nil
|
||||
p.pairValues = nil
|
||||
p.types = nil
|
||||
|
||||
for _, r := range p.runes {
|
||||
props, i := LookupRune(r)
|
||||
bytecount += i
|
||||
cls := props.Class()
|
||||
if cls == B {
|
||||
return bytecount, nil
|
||||
}
|
||||
p.types = append(p.types, cls)
|
||||
if props.IsOpeningBracket() {
|
||||
p.pairTypes = append(p.pairTypes, bpOpen)
|
||||
p.pairValues = append(p.pairValues, r)
|
||||
} else if props.IsBracket() {
|
||||
// this must be a closing bracket,
|
||||
// since IsOpeningBracket is not true
|
||||
p.pairTypes = append(p.pairTypes, bpClose)
|
||||
p.pairValues = append(p.pairValues, r)
|
||||
} else {
|
||||
p.pairTypes = append(p.pairTypes, bpNone)
|
||||
p.pairValues = append(p.pairValues, 0)
|
||||
}
|
||||
}
|
||||
return bytecount, nil
|
||||
}
|
||||
|
||||
// SetBytes configures p for the given paragraph text. It replaces text
|
||||
@@ -80,70 +131,150 @@ type Paragraph struct {
|
||||
// consumed from b including this separator. Error may be non-nil if options are
|
||||
// given.
|
||||
func (p *Paragraph) SetBytes(b []byte, opts ...Option) (n int, err error) {
|
||||
panic("unimplemented")
|
||||
p.p = b
|
||||
p.opts = opts
|
||||
return p.prepareInput()
|
||||
}
|
||||
|
||||
// SetString configures p for the given paragraph text. It replaces text
|
||||
// previously set by SetBytes or SetString. If b contains a paragraph separator
|
||||
// SetString configures s for the given paragraph text. It replaces text
|
||||
// previously set by SetBytes or SetString. If s contains a paragraph separator
|
||||
// it will only process the first paragraph and report the number of bytes
|
||||
// consumed from b including this separator. Error may be non-nil if options are
|
||||
// consumed from s including this separator. Error may be non-nil if options are
|
||||
// given.
|
||||
func (p *Paragraph) SetString(s string, opts ...Option) (n int, err error) {
|
||||
panic("unimplemented")
|
||||
p.p = []byte(s)
|
||||
p.opts = opts
|
||||
return p.prepareInput()
|
||||
}
|
||||
|
||||
// IsLeftToRight reports whether the principle direction of rendering for this
|
||||
// paragraphs is left-to-right. If this returns false, the principle direction
|
||||
// of rendering is right-to-left.
|
||||
func (p *Paragraph) IsLeftToRight() bool {
|
||||
panic("unimplemented")
|
||||
return p.Direction() == LeftToRight
|
||||
}
|
||||
|
||||
// Direction returns the direction of the text of this paragraph.
|
||||
//
|
||||
// The direction may be LeftToRight, RightToLeft, Mixed, or Neutral.
|
||||
func (p *Paragraph) Direction() Direction {
|
||||
panic("unimplemented")
|
||||
return p.o.Direction()
|
||||
}
|
||||
|
||||
// TODO: what happens if the position is > len(input)? This should return an error.
|
||||
|
||||
// RunAt reports the Run at the given position of the input text.
|
||||
//
|
||||
// This method can be used for computing line breaks on paragraphs.
|
||||
func (p *Paragraph) RunAt(pos int) Run {
|
||||
panic("unimplemented")
|
||||
c := 0
|
||||
runNumber := 0
|
||||
for i, r := range p.o.runes {
|
||||
c += len(r)
|
||||
if pos < c {
|
||||
runNumber = i
|
||||
}
|
||||
}
|
||||
return p.o.Run(runNumber)
|
||||
}
|
||||
|
||||
func calculateOrdering(levels []level, runes []rune) Ordering {
|
||||
var curDir Direction
|
||||
|
||||
prevDir := Neutral
|
||||
prevI := 0
|
||||
|
||||
o := Ordering{}
|
||||
// lvl = 0,2,4,...: left to right
|
||||
// lvl = 1,3,5,...: right to left
|
||||
for i, lvl := range levels {
|
||||
if lvl%2 == 0 {
|
||||
curDir = LeftToRight
|
||||
} else {
|
||||
curDir = RightToLeft
|
||||
}
|
||||
if curDir != prevDir {
|
||||
if i > 0 {
|
||||
o.runes = append(o.runes, runes[prevI:i])
|
||||
o.directions = append(o.directions, prevDir)
|
||||
o.startpos = append(o.startpos, prevI)
|
||||
}
|
||||
prevI = i
|
||||
prevDir = curDir
|
||||
}
|
||||
}
|
||||
o.runes = append(o.runes, runes[prevI:])
|
||||
o.directions = append(o.directions, prevDir)
|
||||
o.startpos = append(o.startpos, prevI)
|
||||
return o
|
||||
}
|
||||
|
||||
// Order computes the visual ordering of all the runs in a Paragraph.
|
||||
func (p *Paragraph) Order() (Ordering, error) {
|
||||
panic("unimplemented")
|
||||
if len(p.types) == 0 {
|
||||
return Ordering{}, nil
|
||||
}
|
||||
|
||||
for _, fn := range p.opts {
|
||||
fn(&p.options)
|
||||
}
|
||||
lvl := level(-1)
|
||||
if p.options.defaultDirection == RightToLeft {
|
||||
lvl = 1
|
||||
}
|
||||
para, err := newParagraph(p.types, p.pairTypes, p.pairValues, lvl)
|
||||
if err != nil {
|
||||
return Ordering{}, err
|
||||
}
|
||||
|
||||
levels := para.getLevels([]int{len(p.types)})
|
||||
|
||||
p.o = calculateOrdering(levels, p.runes)
|
||||
return p.o, nil
|
||||
}
|
||||
|
||||
// Line computes the visual ordering of runs for a single line starting and
|
||||
// ending at the given positions in the original text.
|
||||
func (p *Paragraph) Line(start, end int) (Ordering, error) {
|
||||
panic("unimplemented")
|
||||
lineTypes := p.types[start:end]
|
||||
para, err := newParagraph(lineTypes, p.pairTypes[start:end], p.pairValues[start:end], -1)
|
||||
if err != nil {
|
||||
return Ordering{}, err
|
||||
}
|
||||
levels := para.getLevels([]int{len(lineTypes)})
|
||||
o := calculateOrdering(levels, p.runes[start:end])
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// An Ordering holds the computed visual order of runs of a Paragraph. Calling
|
||||
// SetBytes or SetString on the originating Paragraph invalidates an Ordering.
|
||||
// The methods of an Ordering should only be called by one goroutine at a time.
|
||||
type Ordering struct{}
|
||||
type Ordering struct {
|
||||
runes [][]rune
|
||||
directions []Direction
|
||||
startpos []int
|
||||
}
|
||||
|
||||
// Direction reports the directionality of the runs.
|
||||
//
|
||||
// The direction may be LeftToRight, RightToLeft, Mixed, or Neutral.
|
||||
func (o *Ordering) Direction() Direction {
|
||||
panic("unimplemented")
|
||||
return o.directions[0]
|
||||
}
|
||||
|
||||
// NumRuns returns the number of runs.
|
||||
func (o *Ordering) NumRuns() int {
|
||||
panic("unimplemented")
|
||||
return len(o.runes)
|
||||
}
|
||||
|
||||
// Run returns the ith run within the ordering.
|
||||
func (o *Ordering) Run(i int) Run {
|
||||
panic("unimplemented")
|
||||
r := Run{
|
||||
runes: o.runes[i],
|
||||
direction: o.directions[i],
|
||||
startpos: o.startpos[i],
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// TODO: perhaps with options.
|
||||
@@ -155,16 +286,19 @@ func (o *Ordering) Run(i int) Run {
|
||||
|
||||
// A Run is a continuous sequence of characters of a single direction.
|
||||
type Run struct {
|
||||
runes []rune
|
||||
direction Direction
|
||||
startpos int
|
||||
}
|
||||
|
||||
// String returns the text of the run in its original order.
|
||||
func (r *Run) String() string {
|
||||
panic("unimplemented")
|
||||
return string(r.runes)
|
||||
}
|
||||
|
||||
// Bytes returns the text of the run in its original order.
|
||||
func (r *Run) Bytes() []byte {
|
||||
panic("unimplemented")
|
||||
return []byte(r.String())
|
||||
}
|
||||
|
||||
// TODO: methods for
|
||||
@@ -174,25 +308,52 @@ func (r *Run) Bytes() []byte {
|
||||
|
||||
// Direction reports the direction of the run.
|
||||
func (r *Run) Direction() Direction {
|
||||
panic("unimplemented")
|
||||
return r.direction
|
||||
}
|
||||
|
||||
// Position of the Run within the text passed to SetBytes or SetString of the
|
||||
// Pos returns the position of the Run within the text passed to SetBytes or SetString of the
|
||||
// originating Paragraph value.
|
||||
func (r *Run) Pos() (start, end int) {
|
||||
panic("unimplemented")
|
||||
return r.startpos, r.startpos + len(r.runes) - 1
|
||||
}
|
||||
|
||||
// AppendReverse reverses the order of characters of in, appends them to out,
|
||||
// and returns the result. Modifiers will still follow the runes they modify.
|
||||
// Brackets are replaced with their counterparts.
|
||||
func AppendReverse(out, in []byte) []byte {
|
||||
panic("unimplemented")
|
||||
ret := make([]byte, len(in)+len(out))
|
||||
copy(ret, out)
|
||||
inRunes := bytes.Runes(in)
|
||||
|
||||
for i, r := range inRunes {
|
||||
prop, _ := LookupRune(r)
|
||||
if prop.IsBracket() {
|
||||
inRunes[i] = prop.reverseBracket(r)
|
||||
}
|
||||
}
|
||||
|
||||
for i, j := 0, len(inRunes)-1; i < j; i, j = i+1, j-1 {
|
||||
inRunes[i], inRunes[j] = inRunes[j], inRunes[i]
|
||||
}
|
||||
copy(ret[len(out):], string(inRunes))
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ReverseString reverses the order of characters in s and returns a new string.
|
||||
// Modifiers will still follow the runes they modify. Brackets are replaced with
|
||||
// their counterparts.
|
||||
func ReverseString(s string) string {
|
||||
panic("unimplemented")
|
||||
input := []rune(s)
|
||||
li := len(input)
|
||||
ret := make([]rune, li)
|
||||
for i, r := range input {
|
||||
prop, _ := LookupRune(r)
|
||||
if prop.IsBracket() {
|
||||
ret[li-i-1] = prop.reverseBracket(r)
|
||||
} else {
|
||||
ret[li-i-1] = r
|
||||
}
|
||||
}
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// This file contains a port of the reference implementation of the
|
||||
// Bidi Parentheses Algorithm:
|
||||
// http://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java
|
||||
// https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java
|
||||
//
|
||||
// The implementation in this file covers definitions BD14-BD16 and rule N0
|
||||
// of UAX#9.
|
||||
@@ -246,7 +246,7 @@ func (p *bracketPairer) getStrongTypeN0(index int) Class {
|
||||
// assuming the given embedding direction.
|
||||
//
|
||||
// It returns ON if no strong type is found. If a single strong type is found,
|
||||
// it returns this this type. Otherwise it returns the embedding direction.
|
||||
// it returns this type. Otherwise it returns the embedding direction.
|
||||
//
|
||||
// TODO: use separate type for "strong" directionality.
|
||||
func (p *bracketPairer) classifyPairContent(loc bracketPair, dirEmbed Class) Class {
|
||||
|
||||
+57
-51
@@ -4,10 +4,13 @@
|
||||
|
||||
package bidi
|
||||
|
||||
import "log"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// This implementation is a port based on the reference implementation found at:
|
||||
// http://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/
|
||||
// https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/
|
||||
//
|
||||
// described in Unicode Bidirectional Algorithm (UAX #9).
|
||||
//
|
||||
@@ -97,13 +100,20 @@ type paragraph struct {
|
||||
// rune (suggested is the rune of the open bracket for opening and matching
|
||||
// close brackets, after normalization). The embedding levels are optional, but
|
||||
// may be supplied to encode embedding levels of styled text.
|
||||
//
|
||||
// TODO: return an error.
|
||||
func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, levels level) *paragraph {
|
||||
validateTypes(types)
|
||||
validatePbTypes(pairTypes)
|
||||
validatePbValues(pairValues, pairTypes)
|
||||
validateParagraphEmbeddingLevel(levels)
|
||||
func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, levels level) (*paragraph, error) {
|
||||
var err error
|
||||
if err = validateTypes(types); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validatePbTypes(pairTypes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validatePbValues(pairValues, pairTypes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validateParagraphEmbeddingLevel(levels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := ¶graph{
|
||||
initialTypes: append([]Class(nil), types...),
|
||||
@@ -115,7 +125,7 @@ func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, lev
|
||||
resultTypes: append([]Class(nil), types...),
|
||||
}
|
||||
p.run()
|
||||
return p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *paragraph) Len() int { return len(p.initialTypes) }
|
||||
@@ -183,14 +193,14 @@ func (p *paragraph) run() {
|
||||
//
|
||||
// At the end of this function:
|
||||
//
|
||||
// - The member variable matchingPDI is set to point to the index of the
|
||||
// matching PDI character for each isolate initiator character. If there is
|
||||
// no matching PDI, it is set to the length of the input text. For other
|
||||
// characters, it is set to -1.
|
||||
// - The member variable matchingIsolateInitiator is set to point to the
|
||||
// index of the matching isolate initiator character for each PDI character.
|
||||
// If there is no matching isolate initiator, or the character is not a PDI,
|
||||
// it is set to -1.
|
||||
// - The member variable matchingPDI is set to point to the index of the
|
||||
// matching PDI character for each isolate initiator character. If there is
|
||||
// no matching PDI, it is set to the length of the input text. For other
|
||||
// characters, it is set to -1.
|
||||
// - The member variable matchingIsolateInitiator is set to point to the
|
||||
// index of the matching isolate initiator character for each PDI character.
|
||||
// If there is no matching isolate initiator, or the character is not a PDI,
|
||||
// it is set to -1.
|
||||
func (p *paragraph) determineMatchingIsolates() {
|
||||
p.matchingPDI = make([]int, p.Len())
|
||||
p.matchingIsolateInitiator = make([]int, p.Len())
|
||||
@@ -417,15 +427,8 @@ type isolatingRunSequence struct {
|
||||
|
||||
func (i *isolatingRunSequence) Len() int { return len(i.indexes) }
|
||||
|
||||
func maxLevel(a, b level) level {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Rule X10, second bullet: Determine the start-of-sequence (sos) and end-of-sequence (eos) types,
|
||||
// either L or R, for each isolating run sequence.
|
||||
// either L or R, for each isolating run sequence.
|
||||
func (p *paragraph) isolatingRunSequence(indexes []int) *isolatingRunSequence {
|
||||
length := len(indexes)
|
||||
types := make([]Class, length)
|
||||
@@ -464,8 +467,8 @@ func (p *paragraph) isolatingRunSequence(indexes []int) *isolatingRunSequence {
|
||||
indexes: indexes,
|
||||
types: types,
|
||||
level: level,
|
||||
sos: typeForLevel(maxLevel(prevLevel, level)),
|
||||
eos: typeForLevel(maxLevel(succLevel, level)),
|
||||
sos: typeForLevel(max(prevLevel, level)),
|
||||
eos: typeForLevel(max(succLevel, level)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,15 +483,15 @@ func (s *isolatingRunSequence) resolveWeakTypes() {
|
||||
|
||||
// Rule W1.
|
||||
// Changes all NSMs.
|
||||
preceedingCharacterType := s.sos
|
||||
precedingCharacterType := s.sos
|
||||
for i, t := range s.types {
|
||||
if t == NSM {
|
||||
s.types[i] = preceedingCharacterType
|
||||
s.types[i] = precedingCharacterType
|
||||
} else {
|
||||
if t.in(LRI, RLI, FSI, PDI) {
|
||||
preceedingCharacterType = ON
|
||||
}
|
||||
preceedingCharacterType = t
|
||||
// if t.in(LRI, RLI, FSI, PDI) {
|
||||
// precedingCharacterType = ON
|
||||
// }
|
||||
precedingCharacterType = t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,7 +898,7 @@ func (p *paragraph) getLevels(linebreaks []int) []level {
|
||||
// Lines are concatenated from left to right. So for example, the fifth
|
||||
// character from the left on the third line is
|
||||
//
|
||||
// getReordering(linebreaks)[linebreaks[1] + 4]
|
||||
// getReordering(linebreaks)[linebreaks[1] + 4]
|
||||
//
|
||||
// (linebreaks[1] is the position after the last character of the second
|
||||
// line, which is also the index of the first character on the third line,
|
||||
@@ -1001,58 +1004,61 @@ func typeForLevel(level level) Class {
|
||||
return R
|
||||
}
|
||||
|
||||
// TODO: change validation to not panic
|
||||
|
||||
func validateTypes(types []Class) {
|
||||
func validateTypes(types []Class) error {
|
||||
if len(types) == 0 {
|
||||
log.Panic("types is null")
|
||||
return fmt.Errorf("types is null")
|
||||
}
|
||||
for i, t := range types[:len(types)-1] {
|
||||
if t == B {
|
||||
log.Panicf("B type before end of paragraph at index: %d", i)
|
||||
return fmt.Errorf("B type before end of paragraph at index: %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParagraphEmbeddingLevel(embeddingLevel level) {
|
||||
func validateParagraphEmbeddingLevel(embeddingLevel level) error {
|
||||
if embeddingLevel != implicitLevel &&
|
||||
embeddingLevel != 0 &&
|
||||
embeddingLevel != 1 {
|
||||
log.Panicf("illegal paragraph embedding level: %d", embeddingLevel)
|
||||
return fmt.Errorf("illegal paragraph embedding level: %d", embeddingLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLineBreaks(linebreaks []int, textLength int) {
|
||||
func validateLineBreaks(linebreaks []int, textLength int) error {
|
||||
prev := 0
|
||||
for i, next := range linebreaks {
|
||||
if next <= prev {
|
||||
log.Panicf("bad linebreak: %d at index: %d", next, i)
|
||||
return fmt.Errorf("bad linebreak: %d at index: %d", next, i)
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
if prev != textLength {
|
||||
log.Panicf("last linebreak was %d, want %d", prev, textLength)
|
||||
return fmt.Errorf("last linebreak was %d, want %d", prev, textLength)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePbTypes(pairTypes []bracketType) {
|
||||
func validatePbTypes(pairTypes []bracketType) error {
|
||||
if len(pairTypes) == 0 {
|
||||
log.Panic("pairTypes is null")
|
||||
return fmt.Errorf("pairTypes is null")
|
||||
}
|
||||
for i, pt := range pairTypes {
|
||||
switch pt {
|
||||
case bpNone, bpOpen, bpClose:
|
||||
default:
|
||||
log.Panicf("illegal pairType value at %d: %v", i, pairTypes[i])
|
||||
return fmt.Errorf("illegal pairType value at %d: %v", i, pairTypes[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePbValues(pairValues []rune, pairTypes []bracketType) {
|
||||
func validatePbValues(pairValues []rune, pairTypes []bracketType) error {
|
||||
if pairValues == nil {
|
||||
log.Panic("pairValues is null")
|
||||
return fmt.Errorf("pairValues is null")
|
||||
}
|
||||
if len(pairTypes) != len(pairValues) {
|
||||
log.Panic("pairTypes is different length from pairValues")
|
||||
return fmt.Errorf("pairTypes is different length from pairValues")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"golang.org/x/text/internal/gen"
|
||||
"golang.org/x/text/internal/triegen"
|
||||
"golang.org/x/text/internal/ucd"
|
||||
)
|
||||
|
||||
var outputFile = flag.String("out", "tables.go", "output file")
|
||||
|
||||
func main() {
|
||||
gen.Init()
|
||||
gen.Repackage("gen_trieval.go", "trieval.go", "bidi")
|
||||
gen.Repackage("gen_ranges.go", "ranges_test.go", "bidi")
|
||||
|
||||
genTables()
|
||||
}
|
||||
|
||||
// bidiClass names and codes taken from class "bc" in
|
||||
// http://www.unicode.org/Public/8.0.0/ucd/PropertyValueAliases.txt
|
||||
var bidiClass = map[string]Class{
|
||||
"AL": AL, // ArabicLetter
|
||||
"AN": AN, // ArabicNumber
|
||||
"B": B, // ParagraphSeparator
|
||||
"BN": BN, // BoundaryNeutral
|
||||
"CS": CS, // CommonSeparator
|
||||
"EN": EN, // EuropeanNumber
|
||||
"ES": ES, // EuropeanSeparator
|
||||
"ET": ET, // EuropeanTerminator
|
||||
"L": L, // LeftToRight
|
||||
"NSM": NSM, // NonspacingMark
|
||||
"ON": ON, // OtherNeutral
|
||||
"R": R, // RightToLeft
|
||||
"S": S, // SegmentSeparator
|
||||
"WS": WS, // WhiteSpace
|
||||
|
||||
"FSI": Control,
|
||||
"PDF": Control,
|
||||
"PDI": Control,
|
||||
"LRE": Control,
|
||||
"LRI": Control,
|
||||
"LRO": Control,
|
||||
"RLE": Control,
|
||||
"RLI": Control,
|
||||
"RLO": Control,
|
||||
}
|
||||
|
||||
func genTables() {
|
||||
if numClass > 0x0F {
|
||||
log.Fatalf("Too many Class constants (%#x > 0x0F).", numClass)
|
||||
}
|
||||
w := gen.NewCodeWriter()
|
||||
defer w.WriteVersionedGoFile(*outputFile, "bidi")
|
||||
|
||||
gen.WriteUnicodeVersion(w)
|
||||
|
||||
t := triegen.NewTrie("bidi")
|
||||
|
||||
// Build data about bracket mapping. These bits need to be or-ed with
|
||||
// any other bits.
|
||||
orMask := map[rune]uint64{}
|
||||
|
||||
xorMap := map[rune]int{}
|
||||
xorMasks := []rune{0} // First value is no-op.
|
||||
|
||||
ucd.Parse(gen.OpenUCDFile("BidiBrackets.txt"), func(p *ucd.Parser) {
|
||||
r1 := p.Rune(0)
|
||||
r2 := p.Rune(1)
|
||||
xor := r1 ^ r2
|
||||
if _, ok := xorMap[xor]; !ok {
|
||||
xorMap[xor] = len(xorMasks)
|
||||
xorMasks = append(xorMasks, xor)
|
||||
}
|
||||
entry := uint64(xorMap[xor]) << xorMaskShift
|
||||
switch p.String(2) {
|
||||
case "o":
|
||||
entry |= openMask
|
||||
case "c", "n":
|
||||
default:
|
||||
log.Fatalf("Unknown bracket class %q.", p.String(2))
|
||||
}
|
||||
orMask[r1] = entry
|
||||
})
|
||||
|
||||
w.WriteComment(`
|
||||
xorMasks contains masks to be xor-ed with brackets to get the reverse
|
||||
version.`)
|
||||
w.WriteVar("xorMasks", xorMasks)
|
||||
|
||||
done := map[rune]bool{}
|
||||
|
||||
insert := func(r rune, c Class) {
|
||||
if !done[r] {
|
||||
t.Insert(r, orMask[r]|uint64(c))
|
||||
done[r] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the derived BiDi properties.
|
||||
ucd.Parse(gen.OpenUCDFile("extracted/DerivedBidiClass.txt"), func(p *ucd.Parser) {
|
||||
r := p.Rune(0)
|
||||
class, ok := bidiClass[p.String(1)]
|
||||
if !ok {
|
||||
log.Fatalf("%U: Unknown BiDi class %q", r, p.String(1))
|
||||
}
|
||||
insert(r, class)
|
||||
})
|
||||
visitDefaults(insert)
|
||||
|
||||
// TODO: use sparse blocks. This would reduce table size considerably
|
||||
// from the looks of it.
|
||||
|
||||
sz, err := t.Gen(w)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w.Size += sz
|
||||
}
|
||||
|
||||
// dummy values to make methods in gen_common compile. The real versions
|
||||
// will be generated by this file to tables.go.
|
||||
var (
|
||||
xorMasks []rune
|
||||
)
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/internal/gen"
|
||||
"golang.org/x/text/internal/ucd"
|
||||
"golang.org/x/text/unicode/rangetable"
|
||||
)
|
||||
|
||||
// These tables are hand-extracted from:
|
||||
// http://www.unicode.org/Public/8.0.0/ucd/extracted/DerivedBidiClass.txt
|
||||
func visitDefaults(fn func(r rune, c Class)) {
|
||||
// first write default values for ranges listed above.
|
||||
visitRunes(fn, AL, []rune{
|
||||
0x0600, 0x07BF, // Arabic
|
||||
0x08A0, 0x08FF, // Arabic Extended-A
|
||||
0xFB50, 0xFDCF, // Arabic Presentation Forms
|
||||
0xFDF0, 0xFDFF,
|
||||
0xFE70, 0xFEFF,
|
||||
0x0001EE00, 0x0001EEFF, // Arabic Mathematical Alpha Symbols
|
||||
})
|
||||
visitRunes(fn, R, []rune{
|
||||
0x0590, 0x05FF, // Hebrew
|
||||
0x07C0, 0x089F, // Nko et al.
|
||||
0xFB1D, 0xFB4F,
|
||||
0x00010800, 0x00010FFF, // Cypriot Syllabary et. al.
|
||||
0x0001E800, 0x0001EDFF,
|
||||
0x0001EF00, 0x0001EFFF,
|
||||
})
|
||||
visitRunes(fn, ET, []rune{ // European Terminator
|
||||
0x20A0, 0x20Cf, // Currency symbols
|
||||
})
|
||||
rangetable.Visit(unicode.Noncharacter_Code_Point, func(r rune) {
|
||||
fn(r, BN) // Boundary Neutral
|
||||
})
|
||||
ucd.Parse(gen.OpenUCDFile("DerivedCoreProperties.txt"), func(p *ucd.Parser) {
|
||||
if p.String(1) == "Default_Ignorable_Code_Point" {
|
||||
fn(p.Rune(0), BN) // Boundary Neutral
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func visitRunes(fn func(r rune, c Class), c Class, runes []rune) {
|
||||
for i := 0; i < len(runes); i += 2 {
|
||||
lo, hi := runes[i], runes[i+1]
|
||||
for j := lo; j <= hi; j++ {
|
||||
fn(j, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// Class is the Unicode BiDi class. Each rune has a single class.
|
||||
type Class uint
|
||||
|
||||
const (
|
||||
L Class = iota // LeftToRight
|
||||
R // RightToLeft
|
||||
EN // EuropeanNumber
|
||||
ES // EuropeanSeparator
|
||||
ET // EuropeanTerminator
|
||||
AN // ArabicNumber
|
||||
CS // CommonSeparator
|
||||
B // ParagraphSeparator
|
||||
S // SegmentSeparator
|
||||
WS // WhiteSpace
|
||||
ON // OtherNeutral
|
||||
BN // BoundaryNeutral
|
||||
NSM // NonspacingMark
|
||||
AL // ArabicLetter
|
||||
Control // Control LRO - PDI
|
||||
|
||||
numClass
|
||||
|
||||
LRO // LeftToRightOverride
|
||||
RLO // RightToLeftOverride
|
||||
LRE // LeftToRightEmbedding
|
||||
RLE // RightToLeftEmbedding
|
||||
PDF // PopDirectionalFormat
|
||||
LRI // LeftToRightIsolate
|
||||
RLI // RightToLeftIsolate
|
||||
FSI // FirstStrongIsolate
|
||||
PDI // PopDirectionalIsolate
|
||||
|
||||
unknownClass = ^Class(0)
|
||||
)
|
||||
|
||||
var controlToClass = map[rune]Class{
|
||||
0x202D: LRO, // LeftToRightOverride,
|
||||
0x202E: RLO, // RightToLeftOverride,
|
||||
0x202A: LRE, // LeftToRightEmbedding,
|
||||
0x202B: RLE, // RightToLeftEmbedding,
|
||||
0x202C: PDF, // PopDirectionalFormat,
|
||||
0x2066: LRI, // LeftToRightIsolate,
|
||||
0x2067: RLI, // RightToLeftIsolate,
|
||||
0x2068: FSI, // FirstStrongIsolate,
|
||||
0x2069: PDI, // PopDirectionalIsolate,
|
||||
}
|
||||
|
||||
// A trie entry has the following bits:
|
||||
// 7..5 XOR mask for brackets
|
||||
// 4 1: Bracket open, 0: Bracket close
|
||||
// 3..0 Class type
|
||||
|
||||
const (
|
||||
openMask = 0x10
|
||||
xorMaskShift = 5
|
||||
)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// +build go1.10
|
||||
//go:build go1.10 && !go1.13
|
||||
|
||||
package bidi
|
||||
|
||||
|
||||
+1887
File diff suppressed because it is too large
Load Diff
+1923
File diff suppressed because it is too large
Load Diff
+1955
File diff suppressed because it is too large
Load Diff
+2042
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// +build !go1.10
|
||||
//go:build !go1.10
|
||||
|
||||
package bidi
|
||||
|
||||
|
||||
-12
@@ -37,18 +37,6 @@ const (
|
||||
unknownClass = ^Class(0)
|
||||
)
|
||||
|
||||
var controlToClass = map[rune]Class{
|
||||
0x202D: LRO, // LeftToRightOverride,
|
||||
0x202E: RLO, // RightToLeftOverride,
|
||||
0x202A: LRE, // LeftToRightEmbedding,
|
||||
0x202B: RLE, // RightToLeftEmbedding,
|
||||
0x202C: PDF, // PopDirectionalFormat,
|
||||
0x2066: LRI, // LeftToRightIsolate,
|
||||
0x2067: RLI, // RightToLeftIsolate,
|
||||
0x2068: FSI, // FirstStrongIsolate,
|
||||
0x2069: PDI, // PopDirectionalIsolate,
|
||||
}
|
||||
|
||||
// A trie entry has the following bits:
|
||||
// 7..5 XOR mask for brackets
|
||||
// 4 1: Bracket open, 0: Bracket close
|
||||
|
||||
+6
-2
@@ -407,7 +407,7 @@ func decomposeHangul(buf []byte, r rune) int {
|
||||
|
||||
// decomposeHangul algorithmically decomposes a Hangul rune into
|
||||
// its Jamo components.
|
||||
// See http://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
|
||||
// See https://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
|
||||
func (rb *reorderBuffer) decomposeHangul(r rune) {
|
||||
r -= hangulBase
|
||||
x := r % jamoTCount
|
||||
@@ -420,7 +420,7 @@ func (rb *reorderBuffer) decomposeHangul(r rune) {
|
||||
}
|
||||
|
||||
// combineHangul algorithmically combines Jamo character components into Hangul.
|
||||
// See http://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
|
||||
// See https://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
|
||||
func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
||||
b := rb.rune[:]
|
||||
bn := rb.nrune
|
||||
@@ -461,6 +461,10 @@ func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
||||
// It should only be used to recompose a single segment, as it will not
|
||||
// handle alternations between Hangul and non-Hangul characters correctly.
|
||||
func (rb *reorderBuffer) compose() {
|
||||
// Lazily load the map used by the combine func below, but do
|
||||
// it outside of the loop.
|
||||
recompMapOnce.Do(buildRecompMap)
|
||||
|
||||
// UAX #15, section X5 , including Corrigendum #5
|
||||
// "In any character sequence beginning with starter S, a character C is
|
||||
// blocked from S if and only if there is some character B between S
|
||||
|
||||
+25
-5
@@ -4,6 +4,8 @@
|
||||
|
||||
package norm
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// This file contains Form-specific logic and wrappers for data in tables.go.
|
||||
|
||||
// Rune info is stored in a separate trie per composing form. A composing form
|
||||
@@ -11,7 +13,7 @@ package norm
|
||||
// a rune to a uint16. The values take two forms. For v >= 0x8000:
|
||||
// bits
|
||||
// 15: 1 (inverse of NFD_QC bit of qcInfo)
|
||||
// 13..7: qcInfo (see below). isYesD is always true (no decompostion).
|
||||
// 13..7: qcInfo (see below). isYesD is always true (no decomposition).
|
||||
// 6..0: ccc (compressed CCC value).
|
||||
// For v < 0x8000, the respective rune has a decomposition and v is an index
|
||||
// into a byte array of UTF-8 decomposition sequences and additional info and
|
||||
@@ -108,10 +110,11 @@ func (p Properties) BoundaryAfter() bool {
|
||||
}
|
||||
|
||||
// We pack quick check data in 4 bits:
|
||||
// 5: Combines forward (0 == false, 1 == true)
|
||||
// 4..3: NFC_QC Yes(00), No (10), or Maybe (11)
|
||||
// 2: NFD_QC Yes (0) or No (1). No also means there is a decomposition.
|
||||
// 1..0: Number of trailing non-starters.
|
||||
//
|
||||
// 5: Combines forward (0 == false, 1 == true)
|
||||
// 4..3: NFC_QC Yes(00), No (10), or Maybe (11)
|
||||
// 2: NFD_QC Yes (0) or No (1). No also means there is a decomposition.
|
||||
// 1..0: Number of trailing non-starters.
|
||||
//
|
||||
// When all 4 bits are zero, the character is inert, meaning it is never
|
||||
// influenced by normalization.
|
||||
@@ -178,6 +181,17 @@ func (p Properties) TrailCCC() uint8 {
|
||||
return ccc[p.tccc]
|
||||
}
|
||||
|
||||
func buildRecompMap() {
|
||||
recompMap = make(map[uint32]rune, len(recompMapPacked)/8)
|
||||
var buf [8]byte
|
||||
for i := 0; i < len(recompMapPacked); i += 8 {
|
||||
copy(buf[:], recompMapPacked[i:i+8])
|
||||
key := binary.BigEndian.Uint32(buf[:4])
|
||||
val := binary.BigEndian.Uint32(buf[4:])
|
||||
recompMap[key] = rune(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Recomposition
|
||||
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
|
||||
// This clips off the bits of three entries, but we know this will not
|
||||
@@ -186,8 +200,14 @@ func (p Properties) TrailCCC() uint8 {
|
||||
// Note that the recomposition map for NFC and NFKC are identical.
|
||||
|
||||
// combine returns the combined rune or 0 if it doesn't exist.
|
||||
//
|
||||
// The caller is responsible for calling
|
||||
// recompMapOnce.Do(buildRecompMap) sometime before this is called.
|
||||
func combine(a, b rune) rune {
|
||||
key := uint32(uint16(a))<<16 + uint32(uint16(b))
|
||||
if recompMap == nil {
|
||||
panic("caller error") // see func comment
|
||||
}
|
||||
return recompMap[key]
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -128,8 +128,9 @@ func (i *Iter) Next() []byte {
|
||||
func nextASCIIBytes(i *Iter) []byte {
|
||||
p := i.p + 1
|
||||
if p >= i.rb.nsrc {
|
||||
p0 := i.p
|
||||
i.setDone()
|
||||
return i.rb.src.bytes[i.p:p]
|
||||
return i.rb.src.bytes[p0:p]
|
||||
}
|
||||
if i.rb.src.bytes[p] < utf8.RuneSelf {
|
||||
p0 := i.p
|
||||
|
||||
-976
@@ -1,976 +0,0 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Normalization table generator.
|
||||
// Data read from the web.
|
||||
// See forminfo.go for a description of the trie values associated with each rune.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/internal/gen"
|
||||
"golang.org/x/text/internal/triegen"
|
||||
"golang.org/x/text/internal/ucd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gen.Init()
|
||||
loadUnicodeData()
|
||||
compactCCC()
|
||||
loadCompositionExclusions()
|
||||
completeCharFields(FCanonical)
|
||||
completeCharFields(FCompatibility)
|
||||
computeNonStarterCounts()
|
||||
verifyComputed()
|
||||
printChars()
|
||||
testDerived()
|
||||
printTestdata()
|
||||
makeTables()
|
||||
}
|
||||
|
||||
var (
|
||||
tablelist = flag.String("tables",
|
||||
"all",
|
||||
"comma-separated list of which tables to generate; "+
|
||||
"can be 'decomp', 'recomp', 'info' and 'all'")
|
||||
test = flag.Bool("test",
|
||||
false,
|
||||
"test existing tables against DerivedNormalizationProps and generate test data for regression testing")
|
||||
verbose = flag.Bool("verbose",
|
||||
false,
|
||||
"write data to stdout as it is parsed")
|
||||
)
|
||||
|
||||
const MaxChar = 0x10FFFF // anything above this shouldn't exist
|
||||
|
||||
// Quick Check properties of runes allow us to quickly
|
||||
// determine whether a rune may occur in a normal form.
|
||||
// For a given normal form, a rune may be guaranteed to occur
|
||||
// verbatim (QC=Yes), may or may not combine with another
|
||||
// rune (QC=Maybe), or may not occur (QC=No).
|
||||
type QCResult int
|
||||
|
||||
const (
|
||||
QCUnknown QCResult = iota
|
||||
QCYes
|
||||
QCNo
|
||||
QCMaybe
|
||||
)
|
||||
|
||||
func (r QCResult) String() string {
|
||||
switch r {
|
||||
case QCYes:
|
||||
return "Yes"
|
||||
case QCNo:
|
||||
return "No"
|
||||
case QCMaybe:
|
||||
return "Maybe"
|
||||
}
|
||||
return "***UNKNOWN***"
|
||||
}
|
||||
|
||||
const (
|
||||
FCanonical = iota // NFC or NFD
|
||||
FCompatibility // NFKC or NFKD
|
||||
FNumberOfFormTypes
|
||||
)
|
||||
|
||||
const (
|
||||
MComposed = iota // NFC or NFKC
|
||||
MDecomposed // NFD or NFKD
|
||||
MNumberOfModes
|
||||
)
|
||||
|
||||
// This contains only the properties we're interested in.
|
||||
type Char struct {
|
||||
name string
|
||||
codePoint rune // if zero, this index is not a valid code point.
|
||||
ccc uint8 // canonical combining class
|
||||
origCCC uint8
|
||||
excludeInComp bool // from CompositionExclusions.txt
|
||||
compatDecomp bool // it has a compatibility expansion
|
||||
|
||||
nTrailingNonStarters uint8
|
||||
nLeadingNonStarters uint8 // must be equal to trailing if non-zero
|
||||
|
||||
forms [FNumberOfFormTypes]FormInfo // For FCanonical and FCompatibility
|
||||
|
||||
state State
|
||||
}
|
||||
|
||||
var chars = make([]Char, MaxChar+1)
|
||||
var cccMap = make(map[uint8]uint8)
|
||||
|
||||
func (c Char) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(buf, "%U [%s]:\n", c.codePoint, c.name)
|
||||
fmt.Fprintf(buf, " ccc: %v\n", c.ccc)
|
||||
fmt.Fprintf(buf, " excludeInComp: %v\n", c.excludeInComp)
|
||||
fmt.Fprintf(buf, " compatDecomp: %v\n", c.compatDecomp)
|
||||
fmt.Fprintf(buf, " state: %v\n", c.state)
|
||||
fmt.Fprintf(buf, " NFC:\n")
|
||||
fmt.Fprint(buf, c.forms[FCanonical])
|
||||
fmt.Fprintf(buf, " NFKC:\n")
|
||||
fmt.Fprint(buf, c.forms[FCompatibility])
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// In UnicodeData.txt, some ranges are marked like this:
|
||||
// 3400;<CJK Ideograph Extension A, First>;Lo;0;L;;;;;N;;;;;
|
||||
// 4DB5;<CJK Ideograph Extension A, Last>;Lo;0;L;;;;;N;;;;;
|
||||
// parseCharacter keeps a state variable indicating the weirdness.
|
||||
type State int
|
||||
|
||||
const (
|
||||
SNormal State = iota // known to be zero for the type
|
||||
SFirst
|
||||
SLast
|
||||
SMissing
|
||||
)
|
||||
|
||||
var lastChar = rune('\u0000')
|
||||
|
||||
func (c Char) isValid() bool {
|
||||
return c.codePoint != 0 && c.state != SMissing
|
||||
}
|
||||
|
||||
type FormInfo struct {
|
||||
quickCheck [MNumberOfModes]QCResult // index: MComposed or MDecomposed
|
||||
verified [MNumberOfModes]bool // index: MComposed or MDecomposed
|
||||
|
||||
combinesForward bool // May combine with rune on the right
|
||||
combinesBackward bool // May combine with rune on the left
|
||||
isOneWay bool // Never appears in result
|
||||
inDecomp bool // Some decompositions result in this char.
|
||||
decomp Decomposition
|
||||
expandedDecomp Decomposition
|
||||
}
|
||||
|
||||
func (f FormInfo) String() string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0))
|
||||
|
||||
fmt.Fprintf(buf, " quickCheck[C]: %v\n", f.quickCheck[MComposed])
|
||||
fmt.Fprintf(buf, " quickCheck[D]: %v\n", f.quickCheck[MDecomposed])
|
||||
fmt.Fprintf(buf, " cmbForward: %v\n", f.combinesForward)
|
||||
fmt.Fprintf(buf, " cmbBackward: %v\n", f.combinesBackward)
|
||||
fmt.Fprintf(buf, " isOneWay: %v\n", f.isOneWay)
|
||||
fmt.Fprintf(buf, " inDecomp: %v\n", f.inDecomp)
|
||||
fmt.Fprintf(buf, " decomposition: %X\n", f.decomp)
|
||||
fmt.Fprintf(buf, " expandedDecomp: %X\n", f.expandedDecomp)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type Decomposition []rune
|
||||
|
||||
func parseDecomposition(s string, skipfirst bool) (a []rune, err error) {
|
||||
decomp := strings.Split(s, " ")
|
||||
if len(decomp) > 0 && skipfirst {
|
||||
decomp = decomp[1:]
|
||||
}
|
||||
for _, d := range decomp {
|
||||
point, err := strconv.ParseUint(d, 16, 64)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
a = append(a, rune(point))
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func loadUnicodeData() {
|
||||
f := gen.OpenUCDFile("UnicodeData.txt")
|
||||
defer f.Close()
|
||||
p := ucd.New(f)
|
||||
for p.Next() {
|
||||
r := p.Rune(ucd.CodePoint)
|
||||
char := &chars[r]
|
||||
|
||||
char.ccc = uint8(p.Uint(ucd.CanonicalCombiningClass))
|
||||
decmap := p.String(ucd.DecompMapping)
|
||||
|
||||
exp, err := parseDecomposition(decmap, false)
|
||||
isCompat := false
|
||||
if err != nil {
|
||||
if len(decmap) > 0 {
|
||||
exp, err = parseDecomposition(decmap, true)
|
||||
if err != nil {
|
||||
log.Fatalf(`%U: bad decomp |%v|: "%s"`, r, decmap, err)
|
||||
}
|
||||
isCompat = true
|
||||
}
|
||||
}
|
||||
|
||||
char.name = p.String(ucd.Name)
|
||||
char.codePoint = r
|
||||
char.forms[FCompatibility].decomp = exp
|
||||
if !isCompat {
|
||||
char.forms[FCanonical].decomp = exp
|
||||
} else {
|
||||
char.compatDecomp = true
|
||||
}
|
||||
if len(decmap) > 0 {
|
||||
char.forms[FCompatibility].decomp = exp
|
||||
}
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// compactCCC converts the sparse set of CCC values to a continguous one,
|
||||
// reducing the number of bits needed from 8 to 6.
|
||||
func compactCCC() {
|
||||
m := make(map[uint8]uint8)
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
m[c.ccc] = 0
|
||||
}
|
||||
cccs := []int{}
|
||||
for v, _ := range m {
|
||||
cccs = append(cccs, int(v))
|
||||
}
|
||||
sort.Ints(cccs)
|
||||
for i, c := range cccs {
|
||||
cccMap[uint8(i)] = uint8(c)
|
||||
m[uint8(c)] = uint8(i)
|
||||
}
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
c.origCCC = c.ccc
|
||||
c.ccc = m[c.ccc]
|
||||
}
|
||||
if len(m) >= 1<<6 {
|
||||
log.Fatalf("too many difference CCC values: %d >= 64", len(m))
|
||||
}
|
||||
}
|
||||
|
||||
// CompositionExclusions.txt has form:
|
||||
// 0958 # ...
|
||||
// See http://unicode.org/reports/tr44/ for full explanation
|
||||
func loadCompositionExclusions() {
|
||||
f := gen.OpenUCDFile("CompositionExclusions.txt")
|
||||
defer f.Close()
|
||||
p := ucd.New(f)
|
||||
for p.Next() {
|
||||
c := &chars[p.Rune(0)]
|
||||
if c.excludeInComp {
|
||||
log.Fatalf("%U: Duplicate entry in exclusions.", c.codePoint)
|
||||
}
|
||||
c.excludeInComp = true
|
||||
}
|
||||
if e := p.Err(); e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
// hasCompatDecomp returns true if any of the recursive
|
||||
// decompositions contains a compatibility expansion.
|
||||
// In this case, the character may not occur in NFK*.
|
||||
func hasCompatDecomp(r rune) bool {
|
||||
c := &chars[r]
|
||||
if c.compatDecomp {
|
||||
return true
|
||||
}
|
||||
for _, d := range c.forms[FCompatibility].decomp {
|
||||
if hasCompatDecomp(d) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Hangul related constants.
|
||||
const (
|
||||
HangulBase = 0xAC00
|
||||
HangulEnd = 0xD7A4 // hangulBase + Jamo combinations (19 * 21 * 28)
|
||||
|
||||
JamoLBase = 0x1100
|
||||
JamoLEnd = 0x1113
|
||||
JamoVBase = 0x1161
|
||||
JamoVEnd = 0x1176
|
||||
JamoTBase = 0x11A8
|
||||
JamoTEnd = 0x11C3
|
||||
|
||||
JamoLVTCount = 19 * 21 * 28
|
||||
JamoTCount = 28
|
||||
)
|
||||
|
||||
func isHangul(r rune) bool {
|
||||
return HangulBase <= r && r < HangulEnd
|
||||
}
|
||||
|
||||
func isHangulWithoutJamoT(r rune) bool {
|
||||
if !isHangul(r) {
|
||||
return false
|
||||
}
|
||||
r -= HangulBase
|
||||
return r < JamoLVTCount && r%JamoTCount == 0
|
||||
}
|
||||
|
||||
func ccc(r rune) uint8 {
|
||||
return chars[r].ccc
|
||||
}
|
||||
|
||||
// Insert a rune in a buffer, ordered by Canonical Combining Class.
|
||||
func insertOrdered(b Decomposition, r rune) Decomposition {
|
||||
n := len(b)
|
||||
b = append(b, 0)
|
||||
cc := ccc(r)
|
||||
if cc > 0 {
|
||||
// Use bubble sort.
|
||||
for ; n > 0; n-- {
|
||||
if ccc(b[n-1]) <= cc {
|
||||
break
|
||||
}
|
||||
b[n] = b[n-1]
|
||||
}
|
||||
}
|
||||
b[n] = r
|
||||
return b
|
||||
}
|
||||
|
||||
// Recursively decompose.
|
||||
func decomposeRecursive(form int, r rune, d Decomposition) Decomposition {
|
||||
dcomp := chars[r].forms[form].decomp
|
||||
if len(dcomp) == 0 {
|
||||
return insertOrdered(d, r)
|
||||
}
|
||||
for _, c := range dcomp {
|
||||
d = decomposeRecursive(form, c, d)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func completeCharFields(form int) {
|
||||
// Phase 0: pre-expand decomposition.
|
||||
for i := range chars {
|
||||
f := &chars[i].forms[form]
|
||||
if len(f.decomp) == 0 {
|
||||
continue
|
||||
}
|
||||
exp := make(Decomposition, 0)
|
||||
for _, c := range f.decomp {
|
||||
exp = decomposeRecursive(form, c, exp)
|
||||
}
|
||||
f.expandedDecomp = exp
|
||||
}
|
||||
|
||||
// Phase 1: composition exclusion, mark decomposition.
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
f := &c.forms[form]
|
||||
|
||||
// Marks script-specific exclusions and version restricted.
|
||||
f.isOneWay = c.excludeInComp
|
||||
|
||||
// Singletons
|
||||
f.isOneWay = f.isOneWay || len(f.decomp) == 1
|
||||
|
||||
// Non-starter decompositions
|
||||
if len(f.decomp) > 1 {
|
||||
chk := c.ccc != 0 || chars[f.decomp[0]].ccc != 0
|
||||
f.isOneWay = f.isOneWay || chk
|
||||
}
|
||||
|
||||
// Runes that decompose into more than two runes.
|
||||
f.isOneWay = f.isOneWay || len(f.decomp) > 2
|
||||
|
||||
if form == FCompatibility {
|
||||
f.isOneWay = f.isOneWay || hasCompatDecomp(c.codePoint)
|
||||
}
|
||||
|
||||
for _, r := range f.decomp {
|
||||
chars[r].forms[form].inDecomp = true
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: forward and backward combining.
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
f := &c.forms[form]
|
||||
|
||||
if !f.isOneWay && len(f.decomp) == 2 {
|
||||
f0 := &chars[f.decomp[0]].forms[form]
|
||||
f1 := &chars[f.decomp[1]].forms[form]
|
||||
if !f0.isOneWay {
|
||||
f0.combinesForward = true
|
||||
}
|
||||
if !f1.isOneWay {
|
||||
f1.combinesBackward = true
|
||||
}
|
||||
}
|
||||
if isHangulWithoutJamoT(rune(i)) {
|
||||
f.combinesForward = true
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: quick check values.
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
f := &c.forms[form]
|
||||
|
||||
switch {
|
||||
case len(f.decomp) > 0:
|
||||
f.quickCheck[MDecomposed] = QCNo
|
||||
case isHangul(rune(i)):
|
||||
f.quickCheck[MDecomposed] = QCNo
|
||||
default:
|
||||
f.quickCheck[MDecomposed] = QCYes
|
||||
}
|
||||
switch {
|
||||
case f.isOneWay:
|
||||
f.quickCheck[MComposed] = QCNo
|
||||
case (i & 0xffff00) == JamoLBase:
|
||||
f.quickCheck[MComposed] = QCYes
|
||||
if JamoLBase <= i && i < JamoLEnd {
|
||||
f.combinesForward = true
|
||||
}
|
||||
if JamoVBase <= i && i < JamoVEnd {
|
||||
f.quickCheck[MComposed] = QCMaybe
|
||||
f.combinesBackward = true
|
||||
f.combinesForward = true
|
||||
}
|
||||
if JamoTBase <= i && i < JamoTEnd {
|
||||
f.quickCheck[MComposed] = QCMaybe
|
||||
f.combinesBackward = true
|
||||
}
|
||||
case !f.combinesBackward:
|
||||
f.quickCheck[MComposed] = QCYes
|
||||
default:
|
||||
f.quickCheck[MComposed] = QCMaybe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func computeNonStarterCounts() {
|
||||
// Phase 4: leading and trailing non-starter count
|
||||
for i := range chars {
|
||||
c := &chars[i]
|
||||
|
||||
runes := []rune{rune(i)}
|
||||
// We always use FCompatibility so that the CGJ insertion points do not
|
||||
// change for repeated normalizations with different forms.
|
||||
if exp := c.forms[FCompatibility].expandedDecomp; len(exp) > 0 {
|
||||
runes = exp
|
||||
}
|
||||
// We consider runes that combine backwards to be non-starters for the
|
||||
// purpose of Stream-Safe Text Processing.
|
||||
for _, r := range runes {
|
||||
if cr := &chars[r]; cr.ccc == 0 && !cr.forms[FCompatibility].combinesBackward {
|
||||
break
|
||||
}
|
||||
c.nLeadingNonStarters++
|
||||
}
|
||||
for i := len(runes) - 1; i >= 0; i-- {
|
||||
if cr := &chars[runes[i]]; cr.ccc == 0 && !cr.forms[FCompatibility].combinesBackward {
|
||||
break
|
||||
}
|
||||
c.nTrailingNonStarters++
|
||||
}
|
||||
if c.nTrailingNonStarters > 3 {
|
||||
log.Fatalf("%U: Decomposition with more than 3 (%d) trailing modifiers (%U)", i, c.nTrailingNonStarters, runes)
|
||||
}
|
||||
|
||||
if isHangul(rune(i)) {
|
||||
c.nTrailingNonStarters = 2
|
||||
if isHangulWithoutJamoT(rune(i)) {
|
||||
c.nTrailingNonStarters = 1
|
||||
}
|
||||
}
|
||||
|
||||
if l, t := c.nLeadingNonStarters, c.nTrailingNonStarters; l > 0 && l != t {
|
||||
log.Fatalf("%U: number of leading and trailing non-starters should be equal (%d vs %d)", i, l, t)
|
||||
}
|
||||
if t := c.nTrailingNonStarters; t > 3 {
|
||||
log.Fatalf("%U: number of trailing non-starters is %d > 3", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printBytes(w io.Writer, b []byte, name string) {
|
||||
fmt.Fprintf(w, "// %s: %d bytes\n", name, len(b))
|
||||
fmt.Fprintf(w, "var %s = [...]byte {", name)
|
||||
for i, c := range b {
|
||||
switch {
|
||||
case i%64 == 0:
|
||||
fmt.Fprintf(w, "\n// Bytes %x - %x\n", i, i+63)
|
||||
case i%8 == 0:
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
fmt.Fprintf(w, "0x%.2X, ", c)
|
||||
}
|
||||
fmt.Fprint(w, "\n}\n\n")
|
||||
}
|
||||
|
||||
// See forminfo.go for format.
|
||||
func makeEntry(f *FormInfo, c *Char) uint16 {
|
||||
e := uint16(0)
|
||||
if r := c.codePoint; HangulBase <= r && r < HangulEnd {
|
||||
e |= 0x40
|
||||
}
|
||||
if f.combinesForward {
|
||||
e |= 0x20
|
||||
}
|
||||
if f.quickCheck[MDecomposed] == QCNo {
|
||||
e |= 0x4
|
||||
}
|
||||
switch f.quickCheck[MComposed] {
|
||||
case QCYes:
|
||||
case QCNo:
|
||||
e |= 0x10
|
||||
case QCMaybe:
|
||||
e |= 0x18
|
||||
default:
|
||||
log.Fatalf("Illegal quickcheck value %v.", f.quickCheck[MComposed])
|
||||
}
|
||||
e |= uint16(c.nTrailingNonStarters)
|
||||
return e
|
||||
}
|
||||
|
||||
// decompSet keeps track of unique decompositions, grouped by whether
|
||||
// the decomposition is followed by a trailing and/or leading CCC.
|
||||
type decompSet [7]map[string]bool
|
||||
|
||||
const (
|
||||
normalDecomp = iota
|
||||
firstMulti
|
||||
firstCCC
|
||||
endMulti
|
||||
firstLeadingCCC
|
||||
firstCCCZeroExcept
|
||||
firstStarterWithNLead
|
||||
lastDecomp
|
||||
)
|
||||
|
||||
var cname = []string{"firstMulti", "firstCCC", "endMulti", "firstLeadingCCC", "firstCCCZeroExcept", "firstStarterWithNLead", "lastDecomp"}
|
||||
|
||||
func makeDecompSet() decompSet {
|
||||
m := decompSet{}
|
||||
for i := range m {
|
||||
m[i] = make(map[string]bool)
|
||||
}
|
||||
return m
|
||||
}
|
||||
func (m *decompSet) insert(key int, s string) {
|
||||
m[key][s] = true
|
||||
}
|
||||
|
||||
func printCharInfoTables(w io.Writer) int {
|
||||
mkstr := func(r rune, f *FormInfo) (int, string) {
|
||||
d := f.expandedDecomp
|
||||
s := string([]rune(d))
|
||||
if max := 1 << 6; len(s) >= max {
|
||||
const msg = "%U: too many bytes in decomposition: %d >= %d"
|
||||
log.Fatalf(msg, r, len(s), max)
|
||||
}
|
||||
head := uint8(len(s))
|
||||
if f.quickCheck[MComposed] != QCYes {
|
||||
head |= 0x40
|
||||
}
|
||||
if f.combinesForward {
|
||||
head |= 0x80
|
||||
}
|
||||
s = string([]byte{head}) + s
|
||||
|
||||
lccc := ccc(d[0])
|
||||
tccc := ccc(d[len(d)-1])
|
||||
cc := ccc(r)
|
||||
if cc != 0 && lccc == 0 && tccc == 0 {
|
||||
log.Fatalf("%U: trailing and leading ccc are 0 for non-zero ccc %d", r, cc)
|
||||
}
|
||||
if tccc < lccc && lccc != 0 {
|
||||
const msg = "%U: lccc (%d) must be <= tcc (%d)"
|
||||
log.Fatalf(msg, r, lccc, tccc)
|
||||
}
|
||||
index := normalDecomp
|
||||
nTrail := chars[r].nTrailingNonStarters
|
||||
nLead := chars[r].nLeadingNonStarters
|
||||
if tccc > 0 || lccc > 0 || nTrail > 0 {
|
||||
tccc <<= 2
|
||||
tccc |= nTrail
|
||||
s += string([]byte{tccc})
|
||||
index = endMulti
|
||||
for _, r := range d[1:] {
|
||||
if ccc(r) == 0 {
|
||||
index = firstCCC
|
||||
}
|
||||
}
|
||||
if lccc > 0 || nLead > 0 {
|
||||
s += string([]byte{lccc})
|
||||
if index == firstCCC {
|
||||
log.Fatalf("%U: multi-segment decomposition not supported for decompositions with leading CCC != 0", r)
|
||||
}
|
||||
index = firstLeadingCCC
|
||||
}
|
||||
if cc != lccc {
|
||||
if cc != 0 {
|
||||
log.Fatalf("%U: for lccc != ccc, expected ccc to be 0; was %d", r, cc)
|
||||
}
|
||||
index = firstCCCZeroExcept
|
||||
}
|
||||
} else if len(d) > 1 {
|
||||
index = firstMulti
|
||||
}
|
||||
return index, s
|
||||
}
|
||||
|
||||
decompSet := makeDecompSet()
|
||||
const nLeadStr = "\x00\x01" // 0-byte length and tccc with nTrail.
|
||||
decompSet.insert(firstStarterWithNLead, nLeadStr)
|
||||
|
||||
// Store the uniqued decompositions in a byte buffer,
|
||||
// preceded by their byte length.
|
||||
for _, c := range chars {
|
||||
for _, f := range c.forms {
|
||||
if len(f.expandedDecomp) == 0 {
|
||||
continue
|
||||
}
|
||||
if f.combinesBackward {
|
||||
log.Fatalf("%U: combinesBackward and decompose", c.codePoint)
|
||||
}
|
||||
index, s := mkstr(c.codePoint, &f)
|
||||
decompSet.insert(index, s)
|
||||
}
|
||||
}
|
||||
|
||||
decompositions := bytes.NewBuffer(make([]byte, 0, 10000))
|
||||
size := 0
|
||||
positionMap := make(map[string]uint16)
|
||||
decompositions.WriteString("\000")
|
||||
fmt.Fprintln(w, "const (")
|
||||
for i, m := range decompSet {
|
||||
sa := []string{}
|
||||
for s := range m {
|
||||
sa = append(sa, s)
|
||||
}
|
||||
sort.Strings(sa)
|
||||
for _, s := range sa {
|
||||
p := decompositions.Len()
|
||||
decompositions.WriteString(s)
|
||||
positionMap[s] = uint16(p)
|
||||
}
|
||||
if cname[i] != "" {
|
||||
fmt.Fprintf(w, "%s = 0x%X\n", cname[i], decompositions.Len())
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, "maxDecomp = 0x8000")
|
||||
fmt.Fprintln(w, ")")
|
||||
b := decompositions.Bytes()
|
||||
printBytes(w, b, "decomps")
|
||||
size += len(b)
|
||||
|
||||
varnames := []string{"nfc", "nfkc"}
|
||||
for i := 0; i < FNumberOfFormTypes; i++ {
|
||||
trie := triegen.NewTrie(varnames[i])
|
||||
|
||||
for r, c := range chars {
|
||||
f := c.forms[i]
|
||||
d := f.expandedDecomp
|
||||
if len(d) != 0 {
|
||||
_, key := mkstr(c.codePoint, &f)
|
||||
trie.Insert(rune(r), uint64(positionMap[key]))
|
||||
if c.ccc != ccc(d[0]) {
|
||||
// We assume the lead ccc of a decomposition !=0 in this case.
|
||||
if ccc(d[0]) == 0 {
|
||||
log.Fatalf("Expected leading CCC to be non-zero; ccc is %d", c.ccc)
|
||||
}
|
||||
}
|
||||
} else if c.nLeadingNonStarters > 0 && len(f.expandedDecomp) == 0 && c.ccc == 0 && !f.combinesBackward {
|
||||
// Handle cases where it can't be detected that the nLead should be equal
|
||||
// to nTrail.
|
||||
trie.Insert(c.codePoint, uint64(positionMap[nLeadStr]))
|
||||
} else if v := makeEntry(&f, &c)<<8 | uint16(c.ccc); v != 0 {
|
||||
trie.Insert(c.codePoint, uint64(0x8000|v))
|
||||
}
|
||||
}
|
||||
sz, err := trie.Gen(w, triegen.Compact(&normCompacter{name: varnames[i]}))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
size += sz
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func contains(sa []string, s string) bool {
|
||||
for _, a := range sa {
|
||||
if a == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func makeTables() {
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
size := 0
|
||||
if *tablelist == "" {
|
||||
return
|
||||
}
|
||||
list := strings.Split(*tablelist, ",")
|
||||
if *tablelist == "all" {
|
||||
list = []string{"recomp", "info"}
|
||||
}
|
||||
|
||||
// Compute maximum decomposition size.
|
||||
max := 0
|
||||
for _, c := range chars {
|
||||
if n := len(string(c.forms[FCompatibility].expandedDecomp)); n > max {
|
||||
max = n
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "const (")
|
||||
fmt.Fprintln(w, "\t// Version is the Unicode edition from which the tables are derived.")
|
||||
fmt.Fprintf(w, "\tVersion = %q\n", gen.UnicodeVersion())
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, "\t// MaxTransformChunkSize indicates the maximum number of bytes that Transform")
|
||||
fmt.Fprintln(w, "\t// may need to write atomically for any Form. Making a destination buffer at")
|
||||
fmt.Fprintln(w, "\t// least this size ensures that Transform can always make progress and that")
|
||||
fmt.Fprintln(w, "\t// the user does not need to grow the buffer on an ErrShortDst.")
|
||||
fmt.Fprintf(w, "\tMaxTransformChunkSize = %d+maxNonStarters*4\n", len(string(0x034F))+max)
|
||||
fmt.Fprintln(w, ")\n")
|
||||
|
||||
// Print the CCC remap table.
|
||||
size += len(cccMap)
|
||||
fmt.Fprintf(w, "var ccc = [%d]uint8{", len(cccMap))
|
||||
for i := 0; i < len(cccMap); i++ {
|
||||
if i%8 == 0 {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintf(w, "%3d, ", cccMap[uint8(i)])
|
||||
}
|
||||
fmt.Fprintln(w, "\n}\n")
|
||||
|
||||
if contains(list, "info") {
|
||||
size += printCharInfoTables(w)
|
||||
}
|
||||
|
||||
if contains(list, "recomp") {
|
||||
// Note that we use 32 bit keys, instead of 64 bit.
|
||||
// This clips the bits of three entries, but we know
|
||||
// this won't cause a collision. The compiler will catch
|
||||
// any changes made to UnicodeData.txt that introduces
|
||||
// a collision.
|
||||
// Note that the recomposition map for NFC and NFKC
|
||||
// are identical.
|
||||
|
||||
// Recomposition map
|
||||
nrentries := 0
|
||||
for _, c := range chars {
|
||||
f := c.forms[FCanonical]
|
||||
if !f.isOneWay && len(f.decomp) > 0 {
|
||||
nrentries++
|
||||
}
|
||||
}
|
||||
sz := nrentries * 8
|
||||
size += sz
|
||||
fmt.Fprintf(w, "// recompMap: %d bytes (entries only)\n", sz)
|
||||
fmt.Fprintln(w, "var recompMap = map[uint32]rune{")
|
||||
for i, c := range chars {
|
||||
f := c.forms[FCanonical]
|
||||
d := f.decomp
|
||||
if !f.isOneWay && len(d) > 0 {
|
||||
key := uint32(uint16(d[0]))<<16 + uint32(uint16(d[1]))
|
||||
fmt.Fprintf(w, "0x%.8X: 0x%.4X,\n", key, i)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "}\n\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "// Total size of tables: %dKB (%d bytes)\n", (size+512)/1024, size)
|
||||
gen.WriteVersionedGoFile("tables.go", "norm", w.Bytes())
|
||||
}
|
||||
|
||||
func printChars() {
|
||||
if *verbose {
|
||||
for _, c := range chars {
|
||||
if !c.isValid() || c.state == SMissing {
|
||||
continue
|
||||
}
|
||||
fmt.Println(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyComputed does various consistency tests.
|
||||
func verifyComputed() {
|
||||
for i, c := range chars {
|
||||
for _, f := range c.forms {
|
||||
isNo := (f.quickCheck[MDecomposed] == QCNo)
|
||||
if (len(f.decomp) > 0) != isNo && !isHangul(rune(i)) {
|
||||
log.Fatalf("%U: NF*D QC must be No if rune decomposes", i)
|
||||
}
|
||||
|
||||
isMaybe := f.quickCheck[MComposed] == QCMaybe
|
||||
if f.combinesBackward != isMaybe {
|
||||
log.Fatalf("%U: NF*C QC must be Maybe if combinesBackward", i)
|
||||
}
|
||||
if len(f.decomp) > 0 && f.combinesForward && isMaybe {
|
||||
log.Fatalf("%U: NF*C QC must be Yes or No if combinesForward and decomposes", i)
|
||||
}
|
||||
|
||||
if len(f.expandedDecomp) != 0 {
|
||||
continue
|
||||
}
|
||||
if a, b := c.nLeadingNonStarters > 0, (c.ccc > 0 || f.combinesBackward); a != b {
|
||||
// We accept these runes to be treated differently (it only affects
|
||||
// segment breaking in iteration, most likely on improper use), but
|
||||
// reconsider if more characters are added.
|
||||
// U+FF9E HALFWIDTH KATAKANA VOICED SOUND MARK;Lm;0;L;<narrow> 3099;;;;N;;;;;
|
||||
// U+FF9F HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK;Lm;0;L;<narrow> 309A;;;;N;;;;;
|
||||
// U+3133 HANGUL LETTER KIYEOK-SIOS;Lo;0;L;<compat> 11AA;;;;N;HANGUL LETTER GIYEOG SIOS;;;;
|
||||
// U+318E HANGUL LETTER ARAEAE;Lo;0;L;<compat> 11A1;;;;N;HANGUL LETTER ALAE AE;;;;
|
||||
// U+FFA3 HALFWIDTH HANGUL LETTER KIYEOK-SIOS;Lo;0;L;<narrow> 3133;;;;N;HALFWIDTH HANGUL LETTER GIYEOG SIOS;;;;
|
||||
// U+FFDC HALFWIDTH HANGUL LETTER I;Lo;0;L;<narrow> 3163;;;;N;;;;;
|
||||
if i != 0xFF9E && i != 0xFF9F && !(0x3133 <= i && i <= 0x318E) && !(0xFFA3 <= i && i <= 0xFFDC) {
|
||||
log.Fatalf("%U: nLead was %v; want %v", i, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
nfc := c.forms[FCanonical]
|
||||
nfkc := c.forms[FCompatibility]
|
||||
if nfc.combinesBackward != nfkc.combinesBackward {
|
||||
log.Fatalf("%U: Cannot combine combinesBackward\n", c.codePoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use values in DerivedNormalizationProps.txt to compare against the
|
||||
// values we computed.
|
||||
// DerivedNormalizationProps.txt has form:
|
||||
// 00C0..00C5 ; NFD_QC; N # ...
|
||||
// 0374 ; NFD_QC; N # ...
|
||||
// See http://unicode.org/reports/tr44/ for full explanation
|
||||
func testDerived() {
|
||||
f := gen.OpenUCDFile("DerivedNormalizationProps.txt")
|
||||
defer f.Close()
|
||||
p := ucd.New(f)
|
||||
for p.Next() {
|
||||
r := p.Rune(0)
|
||||
c := &chars[r]
|
||||
|
||||
var ftype, mode int
|
||||
qt := p.String(1)
|
||||
switch qt {
|
||||
case "NFC_QC":
|
||||
ftype, mode = FCanonical, MComposed
|
||||
case "NFD_QC":
|
||||
ftype, mode = FCanonical, MDecomposed
|
||||
case "NFKC_QC":
|
||||
ftype, mode = FCompatibility, MComposed
|
||||
case "NFKD_QC":
|
||||
ftype, mode = FCompatibility, MDecomposed
|
||||
default:
|
||||
continue
|
||||
}
|
||||
var qr QCResult
|
||||
switch p.String(2) {
|
||||
case "Y":
|
||||
qr = QCYes
|
||||
case "N":
|
||||
qr = QCNo
|
||||
case "M":
|
||||
qr = QCMaybe
|
||||
default:
|
||||
log.Fatalf(`Unexpected quick check value "%s"`, p.String(2))
|
||||
}
|
||||
if got := c.forms[ftype].quickCheck[mode]; got != qr {
|
||||
log.Printf("%U: FAILED %s (was %v need %v)\n", r, qt, got, qr)
|
||||
}
|
||||
c.forms[ftype].verified[mode] = true
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Any unspecified value must be QCYes. Verify this.
|
||||
for i, c := range chars {
|
||||
for j, fd := range c.forms {
|
||||
for k, qr := range fd.quickCheck {
|
||||
if !fd.verified[k] && qr != QCYes {
|
||||
m := "%U: FAIL F:%d M:%d (was %v need Yes) %s\n"
|
||||
log.Printf(m, i, j, k, qr, c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testHeader = `const (
|
||||
Yes = iota
|
||||
No
|
||||
Maybe
|
||||
)
|
||||
|
||||
type formData struct {
|
||||
qc uint8
|
||||
combinesForward bool
|
||||
decomposition string
|
||||
}
|
||||
|
||||
type runeData struct {
|
||||
r rune
|
||||
ccc uint8
|
||||
nLead uint8
|
||||
nTrail uint8
|
||||
f [2]formData // 0: canonical; 1: compatibility
|
||||
}
|
||||
|
||||
func f(qc uint8, cf bool, dec string) [2]formData {
|
||||
return [2]formData{{qc, cf, dec}, {qc, cf, dec}}
|
||||
}
|
||||
|
||||
func g(qc, qck uint8, cf, cfk bool, d, dk string) [2]formData {
|
||||
return [2]formData{{qc, cf, d}, {qck, cfk, dk}}
|
||||
}
|
||||
|
||||
var testData = []runeData{
|
||||
`
|
||||
|
||||
func printTestdata() {
|
||||
type lastInfo struct {
|
||||
ccc uint8
|
||||
nLead uint8
|
||||
nTrail uint8
|
||||
f string
|
||||
}
|
||||
|
||||
last := lastInfo{}
|
||||
w := &bytes.Buffer{}
|
||||
fmt.Fprintf(w, testHeader)
|
||||
for r, c := range chars {
|
||||
f := c.forms[FCanonical]
|
||||
qc, cf, d := f.quickCheck[MComposed], f.combinesForward, string(f.expandedDecomp)
|
||||
f = c.forms[FCompatibility]
|
||||
qck, cfk, dk := f.quickCheck[MComposed], f.combinesForward, string(f.expandedDecomp)
|
||||
s := ""
|
||||
if d == dk && qc == qck && cf == cfk {
|
||||
s = fmt.Sprintf("f(%s, %v, %q)", qc, cf, d)
|
||||
} else {
|
||||
s = fmt.Sprintf("g(%s, %s, %v, %v, %q, %q)", qc, qck, cf, cfk, d, dk)
|
||||
}
|
||||
current := lastInfo{c.ccc, c.nLeadingNonStarters, c.nTrailingNonStarters, s}
|
||||
if last != current {
|
||||
fmt.Fprintf(w, "\t{0x%x, %d, %d, %d, %s},\n", r, c.origCCC, c.nLeadingNonStarters, c.nTrailingNonStarters, s)
|
||||
last = current
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, "}")
|
||||
gen.WriteVersionedGoFile("data_test.go", "norm", w.Bytes())
|
||||
}
|
||||
+8
-7
@@ -18,19 +18,20 @@ import (
|
||||
// A Form denotes a canonical representation of Unicode code points.
|
||||
// The Unicode-defined normalization and equivalence forms are:
|
||||
//
|
||||
// NFC Unicode Normalization Form C
|
||||
// NFD Unicode Normalization Form D
|
||||
// NFKC Unicode Normalization Form KC
|
||||
// NFKD Unicode Normalization Form KD
|
||||
// NFC Unicode Normalization Form C
|
||||
// NFD Unicode Normalization Form D
|
||||
// NFKC Unicode Normalization Form KC
|
||||
// NFKD Unicode Normalization Form KD
|
||||
//
|
||||
// For a Form f, this documentation uses the notation f(x) to mean
|
||||
// the bytes or string x converted to the given form.
|
||||
// A position n in x is called a boundary if conversion to the form can
|
||||
// proceed independently on both sides:
|
||||
// f(x) == append(f(x[0:n]), f(x[n:])...)
|
||||
//
|
||||
// References: http://unicode.org/reports/tr15/ and
|
||||
// http://unicode.org/notes/tn5/.
|
||||
// f(x) == append(f(x[0:n]), f(x[n:])...)
|
||||
//
|
||||
// References: https://unicode.org/reports/tr15/ and
|
||||
// https://unicode.org/notes/tn5/.
|
||||
type Form int
|
||||
|
||||
const (
|
||||
|
||||
+2
-2
@@ -60,8 +60,8 @@ func (w *normWriter) Close() error {
|
||||
}
|
||||
|
||||
// Writer returns a new writer that implements Write(b)
|
||||
// by writing f(b) to w. The returned writer may use an
|
||||
// an internal buffer to maintain state across Write calls.
|
||||
// by writing f(b) to w. The returned writer may use an
|
||||
// internal buffer to maintain state across Write calls.
|
||||
// Calling its Close method writes any buffered data to w.
|
||||
func (f Form) Writer(w io.Writer) io.WriteCloser {
|
||||
wr := &normWriter{rb: reorderBuffer{}, w: w}
|
||||
|
||||
+948
-944
File diff suppressed because it is too large
Load Diff
+7693
File diff suppressed because it is too large
Load Diff
+7710
File diff suppressed because it is too large
Load Diff
+7760
File diff suppressed because it is too large
Load Diff
+7907
File diff suppressed because it is too large
Load Diff
+948
-944
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -18,7 +18,6 @@ func (Form) Reset() {}
|
||||
// Users should either catch ErrShortDst and allow dst to grow or have dst be at
|
||||
// least of size MaxTransformChunkSize to be guaranteed of progress.
|
||||
func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
n := 0
|
||||
// Cap the maximum number of src bytes to check.
|
||||
b := src
|
||||
eof := atEOF
|
||||
@@ -27,13 +26,14 @@ func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||
eof = false
|
||||
b = b[:ns]
|
||||
}
|
||||
i, ok := formTable[f].quickSpan(inputBytes(b), n, len(b), eof)
|
||||
n += copy(dst[n:], b[n:i])
|
||||
i, ok := formTable[f].quickSpan(inputBytes(b), 0, len(b), eof)
|
||||
n := copy(dst, b[:i])
|
||||
if !ok {
|
||||
nDst, nSrc, err = f.transform(dst[n:], src[n:], atEOF)
|
||||
return nDst + n, nSrc + n, err
|
||||
}
|
||||
if n < len(src) && !atEOF {
|
||||
|
||||
if err == nil && n < len(src) && !atEOF {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return n, n, err
|
||||
@@ -79,7 +79,7 @@ func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||
nSrc += n
|
||||
nDst += n
|
||||
if ok {
|
||||
if n < rb.nsrc && !atEOF {
|
||||
if err == nil && n < rb.nsrc && !atEOF {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return nDst, nSrc, err
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ var (
|
||||
nfkcData = newNfkcTrie(0)
|
||||
)
|
||||
|
||||
// lookupValue determines the type of block n and looks up the value for b.
|
||||
// lookup determines the type of block n and looks up the value for b.
|
||||
// For n < t.cutoff, the block is a simple lookup table. Otherwise, the block
|
||||
// is a list of ranges with an accompanying value. Given a matching range r,
|
||||
// the value for b is by r.value + (b - r.lo) * stride.
|
||||
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Trie table generator.
|
||||
// Used by make*tables tools to generate a go file with trie data structures
|
||||
// for mapping UTF-8 to a 16-bit value. All but the last byte in a UTF-8 byte
|
||||
// sequence are used to lookup offsets in the index table to be used for the
|
||||
// next byte. The last byte is used to index into a table with 16-bit values.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const maxSparseEntries = 16
|
||||
|
||||
type normCompacter struct {
|
||||
sparseBlocks [][]uint64
|
||||
sparseOffset []uint16
|
||||
sparseCount int
|
||||
name string
|
||||
}
|
||||
|
||||
func mostFrequentStride(a []uint64) int {
|
||||
counts := make(map[int]int)
|
||||
var v int
|
||||
for _, x := range a {
|
||||
if stride := int(x) - v; v != 0 && stride >= 0 {
|
||||
counts[stride]++
|
||||
}
|
||||
v = int(x)
|
||||
}
|
||||
var maxs, maxc int
|
||||
for stride, cnt := range counts {
|
||||
if cnt > maxc || (cnt == maxc && stride < maxs) {
|
||||
maxs, maxc = stride, cnt
|
||||
}
|
||||
}
|
||||
return maxs
|
||||
}
|
||||
|
||||
func countSparseEntries(a []uint64) int {
|
||||
stride := mostFrequentStride(a)
|
||||
var v, count int
|
||||
for _, tv := range a {
|
||||
if int(tv)-v != stride {
|
||||
if tv != 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
v = int(tv)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (c *normCompacter) Size(v []uint64) (sz int, ok bool) {
|
||||
if n := countSparseEntries(v); n <= maxSparseEntries {
|
||||
return (n+1)*4 + 2, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (c *normCompacter) Store(v []uint64) uint32 {
|
||||
h := uint32(len(c.sparseOffset))
|
||||
c.sparseBlocks = append(c.sparseBlocks, v)
|
||||
c.sparseOffset = append(c.sparseOffset, uint16(c.sparseCount))
|
||||
c.sparseCount += countSparseEntries(v) + 1
|
||||
return h
|
||||
}
|
||||
|
||||
func (c *normCompacter) Handler() string {
|
||||
return c.name + "Sparse.lookup"
|
||||
}
|
||||
|
||||
func (c *normCompacter) Print(w io.Writer) (retErr error) {
|
||||
p := func(f string, x ...interface{}) {
|
||||
if _, err := fmt.Fprintf(w, f, x...); retErr == nil && err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
|
||||
ls := len(c.sparseBlocks)
|
||||
p("// %sSparseOffset: %d entries, %d bytes\n", c.name, ls, ls*2)
|
||||
p("var %sSparseOffset = %#v\n\n", c.name, c.sparseOffset)
|
||||
|
||||
ns := c.sparseCount
|
||||
p("// %sSparseValues: %d entries, %d bytes\n", c.name, ns, ns*4)
|
||||
p("var %sSparseValues = [%d]valueRange {", c.name, ns)
|
||||
for i, b := range c.sparseBlocks {
|
||||
p("\n// Block %#x, offset %#x", i, c.sparseOffset[i])
|
||||
var v int
|
||||
stride := mostFrequentStride(b)
|
||||
n := countSparseEntries(b)
|
||||
p("\n{value:%#04x,lo:%#02x},", stride, uint8(n))
|
||||
for i, nv := range b {
|
||||
if int(nv)-v != stride {
|
||||
if v != 0 {
|
||||
p(",hi:%#02x},", 0x80+i-1)
|
||||
}
|
||||
if nv != 0 {
|
||||
p("\n{value:%#04x,lo:%#02x", nv, 0x80+i)
|
||||
}
|
||||
}
|
||||
v = int(nv)
|
||||
}
|
||||
if v != 0 {
|
||||
p(",hi:%#02x},", 0x80+len(b)-1)
|
||||
}
|
||||
}
|
||||
p("\n}\n\n")
|
||||
return
|
||||
}
|
||||
Vendored
+27
-4
@@ -1,10 +1,33 @@
|
||||
# golang.org/x/net v0.0.0-20181017193950-04a2e542c03f
|
||||
golang.org/x/net/http2
|
||||
# github.com/google/go-cmp v0.6.0
|
||||
## explicit
|
||||
# github.com/yuin/goldmark v1.4.13
|
||||
## explicit
|
||||
# golang.org/x/crypto v0.46.0
|
||||
## explicit
|
||||
# golang.org/x/mod v0.31.0
|
||||
## explicit
|
||||
# golang.org/x/net v0.48.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/net/http/httpguts
|
||||
golang.org/x/net/http2
|
||||
golang.org/x/net/http2/hpack
|
||||
golang.org/x/net/idna
|
||||
# golang.org/x/text v0.3.0
|
||||
golang.org/x/net/internal/httpcommon
|
||||
# golang.org/x/sync v0.19.0
|
||||
## explicit
|
||||
# golang.org/x/sys v0.39.0
|
||||
## explicit
|
||||
# golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc
|
||||
## explicit
|
||||
# golang.org/x/term v0.38.0
|
||||
## explicit
|
||||
# golang.org/x/text v0.33.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/text/secure/bidirule
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
golang.org/x/text/transform
|
||||
# golang.org/x/tools v0.40.0
|
||||
## explicit
|
||||
# golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
|
||||
## explicit
|
||||
|
||||
Reference in New Issue
Block a user