mirror of https://github.com/k3s-io/k3s
update code change for golang.org/x/net/... to release-branch.go1.10
parent
adf155ee9f
commit
b540527df0
|
@ -5,6 +5,8 @@
|
||||||
// Package context defines the Context type, which carries deadlines,
|
// Package context defines the Context type, which carries deadlines,
|
||||||
// cancelation signals, and other request-scoped values across API boundaries
|
// cancelation signals, and other request-scoped values across API boundaries
|
||||||
// and between processes.
|
// and between processes.
|
||||||
|
// As of Go 1.7 this package is available in the standard library under the
|
||||||
|
// name context. https://golang.org/pkg/context.
|
||||||
//
|
//
|
||||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||||
// servers should accept a Context. The chain of function calls between must
|
// servers should accept a Context. The chain of function calls between must
|
||||||
|
|
|
@ -4,17 +4,17 @@
|
||||||
|
|
||||||
// +build ignore
|
// +build ignore
|
||||||
|
|
||||||
|
//go:generate go run gen.go
|
||||||
|
//go:generate go run gen.go -test
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// This program generates table.go and table_test.go.
|
|
||||||
// Invoke as
|
|
||||||
//
|
|
||||||
// go run gen.go |gofmt >table.go
|
|
||||||
// go run gen.go -test |gofmt >table_test.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -42,6 +42,18 @@ func identifier(s string) string {
|
||||||
|
|
||||||
var test = flag.Bool("test", false, "generate table_test.go")
|
var test = flag.Bool("test", false, "generate table_test.go")
|
||||||
|
|
||||||
|
func genFile(name string, buf *bytes.Buffer) {
|
||||||
|
b, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(name, b, 0644); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -52,32 +64,31 @@ func main() {
|
||||||
all = append(all, extra...)
|
all = append(all, extra...)
|
||||||
sort.Strings(all)
|
sort.Strings(all)
|
||||||
|
|
||||||
if *test {
|
|
||||||
fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n")
|
|
||||||
fmt.Printf("package atom\n\n")
|
|
||||||
fmt.Printf("var testAtomList = []string{\n")
|
|
||||||
for _, s := range all {
|
|
||||||
fmt.Printf("\t%q,\n", s)
|
|
||||||
}
|
|
||||||
fmt.Printf("}\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniq - lists have dups
|
// uniq - lists have dups
|
||||||
// compute max len too
|
|
||||||
maxLen := 0
|
|
||||||
w := 0
|
w := 0
|
||||||
for _, s := range all {
|
for _, s := range all {
|
||||||
if w == 0 || all[w-1] != s {
|
if w == 0 || all[w-1] != s {
|
||||||
if maxLen < len(s) {
|
|
||||||
maxLen = len(s)
|
|
||||||
}
|
|
||||||
all[w] = s
|
all[w] = s
|
||||||
w++
|
w++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
all = all[:w]
|
all = all[:w]
|
||||||
|
|
||||||
|
if *test {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
|
||||||
|
fmt.Fprintln(&buf, "//go:generate go run gen.go -test\n")
|
||||||
|
fmt.Fprintln(&buf, "package atom\n")
|
||||||
|
fmt.Fprintln(&buf, "var testAtomList = []string{")
|
||||||
|
for _, s := range all {
|
||||||
|
fmt.Fprintf(&buf, "\t%q,\n", s)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(&buf, "}")
|
||||||
|
|
||||||
|
genFile("table_test.go", &buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Find hash that minimizes table size.
|
// Find hash that minimizes table size.
|
||||||
var best *table
|
var best *table
|
||||||
for i := 0; i < 1000000; i++ {
|
for i := 0; i < 1000000; i++ {
|
||||||
|
@ -163,36 +174,46 @@ func main() {
|
||||||
atom[s] = uint32(off<<8 | len(s))
|
atom[s] = uint32(off<<8 | len(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
// Generate the Go code.
|
// Generate the Go code.
|
||||||
fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n")
|
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
|
||||||
fmt.Printf("package atom\n\nconst (\n")
|
fmt.Fprintln(&buf, "//go:generate go run gen.go\n")
|
||||||
|
fmt.Fprintln(&buf, "package atom\n\nconst (")
|
||||||
|
|
||||||
|
// compute max len
|
||||||
|
maxLen := 0
|
||||||
for _, s := range all {
|
for _, s := range all {
|
||||||
fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s])
|
if maxLen < len(s) {
|
||||||
|
maxLen = len(s)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "\t%s Atom = %#x\n", identifier(s), atom[s])
|
||||||
}
|
}
|
||||||
fmt.Printf(")\n\n")
|
fmt.Fprintln(&buf, ")\n")
|
||||||
|
|
||||||
fmt.Printf("const hash0 = %#x\n\n", best.h0)
|
fmt.Fprintf(&buf, "const hash0 = %#x\n\n", best.h0)
|
||||||
fmt.Printf("const maxAtomLen = %d\n\n", maxLen)
|
fmt.Fprintf(&buf, "const maxAtomLen = %d\n\n", maxLen)
|
||||||
|
|
||||||
fmt.Printf("var table = [1<<%d]Atom{\n", best.k)
|
fmt.Fprintf(&buf, "var table = [1<<%d]Atom{\n", best.k)
|
||||||
for i, s := range best.tab {
|
for i, s := range best.tab {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s)
|
fmt.Fprintf(&buf, "\t%#x: %#x, // %s\n", i, atom[s], s)
|
||||||
}
|
}
|
||||||
fmt.Printf("}\n")
|
fmt.Fprintf(&buf, "}\n")
|
||||||
datasize := (1 << best.k) * 4
|
datasize := (1 << best.k) * 4
|
||||||
|
|
||||||
fmt.Printf("const atomText =\n")
|
fmt.Fprintln(&buf, "const atomText =")
|
||||||
textsize := len(text)
|
textsize := len(text)
|
||||||
for len(text) > 60 {
|
for len(text) > 60 {
|
||||||
fmt.Printf("\t%q +\n", text[:60])
|
fmt.Fprintf(&buf, "\t%q +\n", text[:60])
|
||||||
text = text[60:]
|
text = text[60:]
|
||||||
}
|
}
|
||||||
fmt.Printf("\t%q\n\n", text)
|
fmt.Fprintf(&buf, "\t%q\n\n", text)
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
|
genFile("table.go", &buf)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
|
||||||
}
|
}
|
||||||
|
|
||||||
type byLen []string
|
type byLen []string
|
||||||
|
@ -285,8 +306,10 @@ func (t *table) push(i uint32, depth int) bool {
|
||||||
|
|
||||||
// The lists of element names and attribute keys were taken from
|
// The lists of element names and attribute keys were taken from
|
||||||
// https://html.spec.whatwg.org/multipage/indices.html#index
|
// https://html.spec.whatwg.org/multipage/indices.html#index
|
||||||
// as of the "HTML Living Standard - Last Updated 21 February 2015" version.
|
// as of the "HTML Living Standard - Last Updated 18 September 2017" version.
|
||||||
|
|
||||||
|
// "command", "keygen" and "menuitem" have been removed from the spec,
|
||||||
|
// but are kept here for backwards compatibility.
|
||||||
var elements = []string{
|
var elements = []string{
|
||||||
"a",
|
"a",
|
||||||
"abbr",
|
"abbr",
|
||||||
|
@ -349,6 +372,7 @@ var elements = []string{
|
||||||
"legend",
|
"legend",
|
||||||
"li",
|
"li",
|
||||||
"link",
|
"link",
|
||||||
|
"main",
|
||||||
"map",
|
"map",
|
||||||
"mark",
|
"mark",
|
||||||
"menu",
|
"menu",
|
||||||
|
@ -364,6 +388,7 @@ var elements = []string{
|
||||||
"output",
|
"output",
|
||||||
"p",
|
"p",
|
||||||
"param",
|
"param",
|
||||||
|
"picture",
|
||||||
"pre",
|
"pre",
|
||||||
"progress",
|
"progress",
|
||||||
"q",
|
"q",
|
||||||
|
@ -375,6 +400,7 @@ var elements = []string{
|
||||||
"script",
|
"script",
|
||||||
"section",
|
"section",
|
||||||
"select",
|
"select",
|
||||||
|
"slot",
|
||||||
"small",
|
"small",
|
||||||
"source",
|
"source",
|
||||||
"span",
|
"span",
|
||||||
|
@ -403,14 +429,21 @@ var elements = []string{
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
|
// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
|
||||||
|
//
|
||||||
|
// "challenge", "command", "contextmenu", "dropzone", "icon", "keytype", "mediagroup",
|
||||||
|
// "radiogroup", "spellcheck", "scoped", "seamless", "sortable" and "sorted" have been removed from the spec,
|
||||||
|
// but are kept here for backwards compatibility.
|
||||||
var attributes = []string{
|
var attributes = []string{
|
||||||
"abbr",
|
"abbr",
|
||||||
"accept",
|
"accept",
|
||||||
"accept-charset",
|
"accept-charset",
|
||||||
"accesskey",
|
"accesskey",
|
||||||
"action",
|
"action",
|
||||||
|
"allowfullscreen",
|
||||||
|
"allowpaymentrequest",
|
||||||
|
"allowusermedia",
|
||||||
"alt",
|
"alt",
|
||||||
|
"as",
|
||||||
"async",
|
"async",
|
||||||
"autocomplete",
|
"autocomplete",
|
||||||
"autofocus",
|
"autofocus",
|
||||||
|
@ -420,6 +453,7 @@ var attributes = []string{
|
||||||
"checked",
|
"checked",
|
||||||
"cite",
|
"cite",
|
||||||
"class",
|
"class",
|
||||||
|
"color",
|
||||||
"cols",
|
"cols",
|
||||||
"colspan",
|
"colspan",
|
||||||
"command",
|
"command",
|
||||||
|
@ -457,6 +491,8 @@ var attributes = []string{
|
||||||
"icon",
|
"icon",
|
||||||
"id",
|
"id",
|
||||||
"inputmode",
|
"inputmode",
|
||||||
|
"integrity",
|
||||||
|
"is",
|
||||||
"ismap",
|
"ismap",
|
||||||
"itemid",
|
"itemid",
|
||||||
"itemprop",
|
"itemprop",
|
||||||
|
@ -481,16 +517,20 @@ var attributes = []string{
|
||||||
"multiple",
|
"multiple",
|
||||||
"muted",
|
"muted",
|
||||||
"name",
|
"name",
|
||||||
|
"nomodule",
|
||||||
|
"nonce",
|
||||||
"novalidate",
|
"novalidate",
|
||||||
"open",
|
"open",
|
||||||
"optimum",
|
"optimum",
|
||||||
"pattern",
|
"pattern",
|
||||||
"ping",
|
"ping",
|
||||||
"placeholder",
|
"placeholder",
|
||||||
|
"playsinline",
|
||||||
"poster",
|
"poster",
|
||||||
"preload",
|
"preload",
|
||||||
"radiogroup",
|
"radiogroup",
|
||||||
"readonly",
|
"readonly",
|
||||||
|
"referrerpolicy",
|
||||||
"rel",
|
"rel",
|
||||||
"required",
|
"required",
|
||||||
"reversed",
|
"reversed",
|
||||||
|
@ -507,10 +547,13 @@ var attributes = []string{
|
||||||
"sizes",
|
"sizes",
|
||||||
"sortable",
|
"sortable",
|
||||||
"sorted",
|
"sorted",
|
||||||
|
"slot",
|
||||||
"span",
|
"span",
|
||||||
|
"spellcheck",
|
||||||
"src",
|
"src",
|
||||||
"srcdoc",
|
"srcdoc",
|
||||||
"srclang",
|
"srclang",
|
||||||
|
"srcset",
|
||||||
"start",
|
"start",
|
||||||
"step",
|
"step",
|
||||||
"style",
|
"style",
|
||||||
|
@ -520,16 +563,22 @@ var attributes = []string{
|
||||||
"translate",
|
"translate",
|
||||||
"type",
|
"type",
|
||||||
"typemustmatch",
|
"typemustmatch",
|
||||||
|
"updateviacache",
|
||||||
"usemap",
|
"usemap",
|
||||||
"value",
|
"value",
|
||||||
"width",
|
"width",
|
||||||
|
"workertype",
|
||||||
"wrap",
|
"wrap",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "onautocomplete", "onautocompleteerror", "onmousewheel",
|
||||||
|
// "onshow" and "onsort" have been removed from the spec,
|
||||||
|
// but are kept here for backwards compatibility.
|
||||||
var eventHandlers = []string{
|
var eventHandlers = []string{
|
||||||
"onabort",
|
"onabort",
|
||||||
"onautocomplete",
|
"onautocomplete",
|
||||||
"onautocompleteerror",
|
"onautocompleteerror",
|
||||||
|
"onauxclick",
|
||||||
"onafterprint",
|
"onafterprint",
|
||||||
"onbeforeprint",
|
"onbeforeprint",
|
||||||
"onbeforeunload",
|
"onbeforeunload",
|
||||||
|
@ -541,11 +590,14 @@ var eventHandlers = []string{
|
||||||
"onclick",
|
"onclick",
|
||||||
"onclose",
|
"onclose",
|
||||||
"oncontextmenu",
|
"oncontextmenu",
|
||||||
|
"oncopy",
|
||||||
"oncuechange",
|
"oncuechange",
|
||||||
|
"oncut",
|
||||||
"ondblclick",
|
"ondblclick",
|
||||||
"ondrag",
|
"ondrag",
|
||||||
"ondragend",
|
"ondragend",
|
||||||
"ondragenter",
|
"ondragenter",
|
||||||
|
"ondragexit",
|
||||||
"ondragleave",
|
"ondragleave",
|
||||||
"ondragover",
|
"ondragover",
|
||||||
"ondragstart",
|
"ondragstart",
|
||||||
|
@ -565,18 +617,24 @@ var eventHandlers = []string{
|
||||||
"onload",
|
"onload",
|
||||||
"onloadeddata",
|
"onloadeddata",
|
||||||
"onloadedmetadata",
|
"onloadedmetadata",
|
||||||
|
"onloadend",
|
||||||
"onloadstart",
|
"onloadstart",
|
||||||
"onmessage",
|
"onmessage",
|
||||||
|
"onmessageerror",
|
||||||
"onmousedown",
|
"onmousedown",
|
||||||
|
"onmouseenter",
|
||||||
|
"onmouseleave",
|
||||||
"onmousemove",
|
"onmousemove",
|
||||||
"onmouseout",
|
"onmouseout",
|
||||||
"onmouseover",
|
"onmouseover",
|
||||||
"onmouseup",
|
"onmouseup",
|
||||||
"onmousewheel",
|
"onmousewheel",
|
||||||
|
"onwheel",
|
||||||
"onoffline",
|
"onoffline",
|
||||||
"ononline",
|
"ononline",
|
||||||
"onpagehide",
|
"onpagehide",
|
||||||
"onpageshow",
|
"onpageshow",
|
||||||
|
"onpaste",
|
||||||
"onpause",
|
"onpause",
|
||||||
"onplay",
|
"onplay",
|
||||||
"onplaying",
|
"onplaying",
|
||||||
|
@ -585,7 +643,9 @@ var eventHandlers = []string{
|
||||||
"onratechange",
|
"onratechange",
|
||||||
"onreset",
|
"onreset",
|
||||||
"onresize",
|
"onresize",
|
||||||
|
"onrejectionhandled",
|
||||||
"onscroll",
|
"onscroll",
|
||||||
|
"onsecuritypolicyviolation",
|
||||||
"onseeked",
|
"onseeked",
|
||||||
"onseeking",
|
"onseeking",
|
||||||
"onselect",
|
"onselect",
|
||||||
|
@ -597,6 +657,7 @@ var eventHandlers = []string{
|
||||||
"onsuspend",
|
"onsuspend",
|
||||||
"ontimeupdate",
|
"ontimeupdate",
|
||||||
"ontoggle",
|
"ontoggle",
|
||||||
|
"onunhandledrejection",
|
||||||
"onunload",
|
"onunload",
|
||||||
"onvolumechange",
|
"onvolumechange",
|
||||||
"onwaiting",
|
"onwaiting",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -52,10 +52,12 @@ var isSpecialElementMap = map[string]bool{
|
||||||
"iframe": true,
|
"iframe": true,
|
||||||
"img": true,
|
"img": true,
|
||||||
"input": true,
|
"input": true,
|
||||||
"isindex": true,
|
"isindex": true, // The 'isindex' element has been removed, but keep it for backwards compatibility.
|
||||||
|
"keygen": true,
|
||||||
"li": true,
|
"li": true,
|
||||||
"link": true,
|
"link": true,
|
||||||
"listing": true,
|
"listing": true,
|
||||||
|
"main": true,
|
||||||
"marquee": true,
|
"marquee": true,
|
||||||
"menu": true,
|
"menu": true,
|
||||||
"meta": true,
|
"meta": true,
|
||||||
|
|
|
@ -49,18 +49,18 @@ call to Next. For example, to extract an HTML page's anchor text:
|
||||||
for {
|
for {
|
||||||
tt := z.Next()
|
tt := z.Next()
|
||||||
switch tt {
|
switch tt {
|
||||||
case ErrorToken:
|
case html.ErrorToken:
|
||||||
return z.Err()
|
return z.Err()
|
||||||
case TextToken:
|
case html.TextToken:
|
||||||
if depth > 0 {
|
if depth > 0 {
|
||||||
// emitBytes should copy the []byte it receives,
|
// emitBytes should copy the []byte it receives,
|
||||||
// if it doesn't process it immediately.
|
// if it doesn't process it immediately.
|
||||||
emitBytes(z.Text())
|
emitBytes(z.Text())
|
||||||
}
|
}
|
||||||
case StartTagToken, EndTagToken:
|
case html.StartTagToken, html.EndTagToken:
|
||||||
tn, _ := z.TagName()
|
tn, _ := z.TagName()
|
||||||
if len(tn) == 1 && tn[0] == 'a' {
|
if len(tn) == 1 && tn[0] == 'a' {
|
||||||
if tt == StartTagToken {
|
if tt == html.StartTagToken {
|
||||||
depth++
|
depth++
|
||||||
} else {
|
} else {
|
||||||
depth--
|
depth--
|
||||||
|
|
|
@ -1161,8 +1161,8 @@ func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool) {
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token returns the next Token. The result's Data and Attr values remain valid
|
// Token returns the current Token. The result's Data and Attr values remain
|
||||||
// after subsequent Next calls.
|
// valid after subsequent Next calls.
|
||||||
func (z *Tokenizer) Token() Token {
|
func (z *Tokenizer) Token() Token {
|
||||||
t := Token{Type: z.tt}
|
t := Token{Type: z.tt}
|
||||||
switch z.tt {
|
switch z.tt {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
package http2
|
package http2
|
||||||
|
|
||||||
// A list of the possible cipher suite ids. Taken from
|
// A list of the possible cipher suite ids. Taken from
|
||||||
// http://www.iana.org/assignments/tls-parameters/tls-parameters.txt
|
// https://www.iana.org/assignments/tls-parameters/tls-parameters.txt
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000
|
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000
|
||||||
|
|
|
@ -73,7 +73,7 @@ type noDialH2RoundTripper struct{ t *Transport }
|
||||||
|
|
||||||
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
res, err := rt.t.RoundTrip(req)
|
res, err := rt.t.RoundTrip(req)
|
||||||
if err == ErrNoCachedConn {
|
if isNoCachedConnError(err) {
|
||||||
return nil, http.ErrSkipAltProtocol
|
return nil, http.ErrSkipAltProtocol
|
||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
|
|
|
@ -220,12 +220,15 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||||
} else if s.TLSConfig.CipherSuites != nil {
|
} else if s.TLSConfig.CipherSuites != nil {
|
||||||
// If they already provided a CipherSuite list, return
|
// If they already provided a CipherSuite list, return
|
||||||
// an error if it has a bad order or is missing
|
// an error if it has a bad order or is missing
|
||||||
// ECDHE_RSA_WITH_AES_128_GCM_SHA256.
|
// ECDHE_RSA_WITH_AES_128_GCM_SHA256 or ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.
|
||||||
const requiredCipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
||||||
haveRequired := false
|
haveRequired := false
|
||||||
sawBad := false
|
sawBad := false
|
||||||
for i, cs := range s.TLSConfig.CipherSuites {
|
for i, cs := range s.TLSConfig.CipherSuites {
|
||||||
if cs == requiredCipher {
|
switch cs {
|
||||||
|
case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
// Alternative MTI cipher to not discourage ECDSA-only servers.
|
||||||
|
// See http://golang.org/cl/30721 for further information.
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
||||||
haveRequired = true
|
haveRequired = true
|
||||||
}
|
}
|
||||||
if isBadCipher(cs) {
|
if isBadCipher(cs) {
|
||||||
|
@ -235,7 +238,7 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !haveRequired {
|
if !haveRequired {
|
||||||
return fmt.Errorf("http2: TLSConfig.CipherSuites is missing HTTP/2-required TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,7 +652,7 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) {
|
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) || err == errPrefaceTimeout {
|
||||||
// Boring, expected errors.
|
// Boring, expected errors.
|
||||||
sc.vlogf(format, args...)
|
sc.vlogf(format, args...)
|
||||||
} else {
|
} else {
|
||||||
|
@ -853,8 +856,13 @@ func (sc *serverConn) serve() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.inGoAway && sc.curOpenStreams() == 0 && !sc.needToSendGoAway && !sc.writingFrame {
|
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
|
||||||
return
|
// with no error code (graceful shutdown), don't start the timer until
|
||||||
|
// all open streams have been completed.
|
||||||
|
sentGoAway := sc.inGoAway && !sc.needToSendGoAway && !sc.writingFrame
|
||||||
|
gracefulShutdownComplete := sc.goAwayCode == ErrCodeNo && sc.curOpenStreams() == 0
|
||||||
|
if sentGoAway && sc.shutdownTimer == nil && (sc.goAwayCode != ErrCodeNo || gracefulShutdownComplete) {
|
||||||
|
sc.shutDownIn(goAwayTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -889,8 +897,11 @@ func (sc *serverConn) sendServeMsg(msg interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPreface reads the ClientPreface greeting from the peer
|
var errPrefaceTimeout = errors.New("timeout waiting for client preface")
|
||||||
// or returns an error on timeout or an invalid greeting.
|
|
||||||
|
// readPreface reads the ClientPreface greeting from the peer or
|
||||||
|
// returns errPrefaceTimeout on timeout, or an error if the greeting
|
||||||
|
// is invalid.
|
||||||
func (sc *serverConn) readPreface() error {
|
func (sc *serverConn) readPreface() error {
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -908,7 +919,7 @@ func (sc *serverConn) readPreface() error {
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return errors.New("timeout waiting for client preface")
|
return errPrefaceTimeout
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if VerboseLogs {
|
if VerboseLogs {
|
||||||
|
@ -1218,30 +1229,31 @@ func (sc *serverConn) startGracefulShutdown() {
|
||||||
sc.shutdownOnce.Do(func() { sc.sendServeMsg(gracefulShutdownMsg) })
|
sc.shutdownOnce.Do(func() { sc.sendServeMsg(gracefulShutdownMsg) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After sending GOAWAY, the connection will close after goAwayTimeout.
|
||||||
|
// If we close the connection immediately after sending GOAWAY, there may
|
||||||
|
// be unsent data in our kernel receive buffer, which will cause the kernel
|
||||||
|
// to send a TCP RST on close() instead of a FIN. This RST will abort the
|
||||||
|
// connection immediately, whether or not the client had received the GOAWAY.
|
||||||
|
//
|
||||||
|
// Ideally we should delay for at least 1 RTT + epsilon so the client has
|
||||||
|
// a chance to read the GOAWAY and stop sending messages. Measuring RTT
|
||||||
|
// is hard, so we approximate with 1 second. See golang.org/issue/18701.
|
||||||
|
//
|
||||||
|
// This is a var so it can be shorter in tests, where all requests uses the
|
||||||
|
// loopback interface making the expected RTT very small.
|
||||||
|
//
|
||||||
|
// TODO: configurable?
|
||||||
|
var goAwayTimeout = 1 * time.Second
|
||||||
|
|
||||||
func (sc *serverConn) startGracefulShutdownInternal() {
|
func (sc *serverConn) startGracefulShutdownInternal() {
|
||||||
sc.goAwayIn(ErrCodeNo, 0)
|
sc.goAway(ErrCodeNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *serverConn) goAway(code ErrCode) {
|
func (sc *serverConn) goAway(code ErrCode) {
|
||||||
sc.serveG.check()
|
|
||||||
var forceCloseIn time.Duration
|
|
||||||
if code != ErrCodeNo {
|
|
||||||
forceCloseIn = 250 * time.Millisecond
|
|
||||||
} else {
|
|
||||||
// TODO: configurable
|
|
||||||
forceCloseIn = 1 * time.Second
|
|
||||||
}
|
|
||||||
sc.goAwayIn(code, forceCloseIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *serverConn) goAwayIn(code ErrCode, forceCloseIn time.Duration) {
|
|
||||||
sc.serveG.check()
|
sc.serveG.check()
|
||||||
if sc.inGoAway {
|
if sc.inGoAway {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if forceCloseIn != 0 {
|
|
||||||
sc.shutDownIn(forceCloseIn)
|
|
||||||
}
|
|
||||||
sc.inGoAway = true
|
sc.inGoAway = true
|
||||||
sc.needToSendGoAway = true
|
sc.needToSendGoAway = true
|
||||||
sc.goAwayCode = code
|
sc.goAwayCode = code
|
||||||
|
@ -2310,7 +2322,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||||
clen = strconv.Itoa(len(p))
|
clen = strconv.Itoa(len(p))
|
||||||
}
|
}
|
||||||
_, hasContentType := rws.snapHeader["Content-Type"]
|
_, hasContentType := rws.snapHeader["Content-Type"]
|
||||||
if !hasContentType && bodyAllowedForStatus(rws.status) {
|
if !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 {
|
||||||
ctype = http.DetectContentType(p)
|
ctype = http.DetectContentType(p)
|
||||||
}
|
}
|
||||||
var date string
|
var date string
|
||||||
|
@ -2478,6 +2490,24 @@ func (w *responseWriter) Header() http.Header {
|
||||||
return rws.handlerHeader
|
return rws.handlerHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkWriteHeaderCode is a copy of net/http's checkWriteHeaderCode.
|
||||||
|
func checkWriteHeaderCode(code int) {
|
||||||
|
// Issue 22880: require valid WriteHeader status codes.
|
||||||
|
// For now we only enforce that it's three digits.
|
||||||
|
// In the future we might block things over 599 (600 and above aren't defined
|
||||||
|
// at http://httpwg.org/specs/rfc7231.html#status.codes)
|
||||||
|
// and we might block under 200 (once we have more mature 1xx support).
|
||||||
|
// But for now any three digits.
|
||||||
|
//
|
||||||
|
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
|
||||||
|
// no equivalent bogus thing we can realistically send in HTTP/2,
|
||||||
|
// so we'll consistently panic instead and help people find their bugs
|
||||||
|
// early. (We can't return an error from WriteHeader even if we wanted to.)
|
||||||
|
if code < 100 || code > 999 {
|
||||||
|
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeader(code int) {
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
rws := w.rws
|
rws := w.rws
|
||||||
if rws == nil {
|
if rws == nil {
|
||||||
|
@ -2488,6 +2518,7 @@ func (w *responseWriter) WriteHeader(code int) {
|
||||||
|
|
||||||
func (rws *responseWriterState) writeHeader(code int) {
|
func (rws *responseWriterState) writeHeader(code int) {
|
||||||
if !rws.wroteHeader {
|
if !rws.wroteHeader {
|
||||||
|
checkWriteHeaderCode(code)
|
||||||
rws.wroteHeader = true
|
rws.wroteHeader = true
|
||||||
rws.status = code
|
rws.status = code
|
||||||
if len(rws.handlerHeader) > 0 {
|
if len(rws.handlerHeader) > 0 {
|
||||||
|
|
|
@ -87,7 +87,7 @@ type Transport struct {
|
||||||
|
|
||||||
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
||||||
// send in the initial settings frame. It is how many bytes
|
// send in the initial settings frame. It is how many bytes
|
||||||
// of response headers are allow. Unlike the http2 spec, zero here
|
// of response headers are allowed. Unlike the http2 spec, zero here
|
||||||
// means to use a default limit (currently 10MB). If you actually
|
// means to use a default limit (currently 10MB). If you actually
|
||||||
// want to advertise an ulimited value to the peer, Transport
|
// want to advertise an ulimited value to the peer, Transport
|
||||||
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
||||||
|
@ -172,9 +172,10 @@ type ClientConn struct {
|
||||||
fr *Framer
|
fr *Framer
|
||||||
lastActive time.Time
|
lastActive time.Time
|
||||||
// Settings from peer: (also guarded by mu)
|
// Settings from peer: (also guarded by mu)
|
||||||
maxFrameSize uint32
|
maxFrameSize uint32
|
||||||
maxConcurrentStreams uint32
|
maxConcurrentStreams uint32
|
||||||
initialWindowSize uint32
|
peerMaxHeaderListSize uint64
|
||||||
|
initialWindowSize uint32
|
||||||
|
|
||||||
hbuf bytes.Buffer // HPACK encoder writes into this
|
hbuf bytes.Buffer // HPACK encoder writes into this
|
||||||
henc *hpack.Encoder
|
henc *hpack.Encoder
|
||||||
|
@ -273,6 +274,13 @@ func (cs *clientStream) checkResetOrDone() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs *clientStream) getStartedWrite() bool {
|
||||||
|
cc := cs.cc
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
return cs.startedWrite
|
||||||
|
}
|
||||||
|
|
||||||
func (cs *clientStream) abortRequestBodyWrite(err error) {
|
func (cs *clientStream) abortRequestBodyWrite(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic("nil error")
|
panic("nil error")
|
||||||
|
@ -298,7 +306,26 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNoCachedConn = errors.New("http2: no cached connection was available")
|
// noCachedConnError is the concrete type of ErrNoCachedConn, which
|
||||||
|
// needs to be detected by net/http regardless of whether it's its
|
||||||
|
// bundled version (in h2_bundle.go with a rewritten type name) or
|
||||||
|
// from a user's x/net/http2. As such, as it has a unique method name
|
||||||
|
// (IsHTTP2NoCachedConnError) that net/http sniffs for via func
|
||||||
|
// isNoCachedConnError.
|
||||||
|
type noCachedConnError struct{}
|
||||||
|
|
||||||
|
func (noCachedConnError) IsHTTP2NoCachedConnError() {}
|
||||||
|
func (noCachedConnError) Error() string { return "http2: no cached connection was available" }
|
||||||
|
|
||||||
|
// isNoCachedConnError reports whether err is of type noCachedConnError
|
||||||
|
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
|
||||||
|
// may coexist in the same running program.
|
||||||
|
func isNoCachedConnError(err error) bool {
|
||||||
|
_, ok := err.(interface{ IsHTTP2NoCachedConnError() })
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNoCachedConn error = noCachedConnError{}
|
||||||
|
|
||||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||||
type RoundTripOpt struct {
|
type RoundTripOpt struct {
|
||||||
|
@ -348,14 +375,9 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
traceGotConn(req, cc)
|
traceGotConn(req, cc)
|
||||||
res, err := cc.RoundTrip(req)
|
res, gotErrAfterReqBodyWrite, err := cc.roundTrip(req)
|
||||||
if err != nil && retry <= 6 {
|
if err != nil && retry <= 6 {
|
||||||
afterBodyWrite := false
|
if req, err = shouldRetryRequest(req, err, gotErrAfterReqBodyWrite); err == nil {
|
||||||
if e, ok := err.(afterReqBodyWriteError); ok {
|
|
||||||
err = e
|
|
||||||
afterBodyWrite = true
|
|
||||||
}
|
|
||||||
if req, err = shouldRetryRequest(req, err, afterBodyWrite); err == nil {
|
|
||||||
// After the first retry, do exponential backoff with 10% jitter.
|
// After the first retry, do exponential backoff with 10% jitter.
|
||||||
if retry == 0 {
|
if retry == 0 {
|
||||||
continue
|
continue
|
||||||
|
@ -393,16 +415,6 @@ var (
|
||||||
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
||||||
)
|
)
|
||||||
|
|
||||||
// afterReqBodyWriteError is a wrapper around errors returned by ClientConn.RoundTrip.
|
|
||||||
// It is used to signal that err happened after part of Request.Body was sent to the server.
|
|
||||||
type afterReqBodyWriteError struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e afterReqBodyWriteError) Error() string {
|
|
||||||
return e.err.Error() + "; some request body already written"
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
||||||
// response headers. It is always called with a non-nil error.
|
// response headers. It is always called with a non-nil error.
|
||||||
// It returns either a request to retry (either the same request, or a
|
// It returns either a request to retry (either the same request, or a
|
||||||
|
@ -519,17 +531,18 @@ func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||||
|
|
||||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
|
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
|
||||||
cc := &ClientConn{
|
cc := &ClientConn{
|
||||||
t: t,
|
t: t,
|
||||||
tconn: c,
|
tconn: c,
|
||||||
readerDone: make(chan struct{}),
|
readerDone: make(chan struct{}),
|
||||||
nextStreamID: 1,
|
nextStreamID: 1,
|
||||||
maxFrameSize: 16 << 10, // spec default
|
maxFrameSize: 16 << 10, // spec default
|
||||||
initialWindowSize: 65535, // spec default
|
initialWindowSize: 65535, // spec default
|
||||||
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
|
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
|
||||||
streams: make(map[uint32]*clientStream),
|
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
|
||||||
singleUse: singleUse,
|
streams: make(map[uint32]*clientStream),
|
||||||
wantSettingsAck: true,
|
singleUse: singleUse,
|
||||||
pings: make(map[[8]byte]chan struct{}),
|
wantSettingsAck: true,
|
||||||
|
pings: make(map[[8]byte]chan struct{}),
|
||||||
}
|
}
|
||||||
if d := t.idleConnTimeout(); d != 0 {
|
if d := t.idleConnTimeout(); d != 0 {
|
||||||
cc.idleTimeout = d
|
cc.idleTimeout = d
|
||||||
|
@ -750,8 +763,13 @@ func actualContentLength(req *http.Request) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, _, err := cc.roundTrip(req)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAfterReqBodyWrite bool, err error) {
|
||||||
if err := checkConnHeaders(req); err != nil {
|
if err := checkConnHeaders(req); err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if cc.idleTimer != nil {
|
if cc.idleTimer != nil {
|
||||||
cc.idleTimer.Stop()
|
cc.idleTimer.Stop()
|
||||||
|
@ -759,14 +777,14 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
trailers, err := commaSeparatedTrailers(req)
|
trailers, err := commaSeparatedTrailers(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
hasTrailers := trailers != ""
|
hasTrailers := trailers != ""
|
||||||
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
if err := cc.awaitOpenSlotForRequest(req); err != nil {
|
if err := cc.awaitOpenSlotForRequest(req); err != nil {
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body := req.Body
|
body := req.Body
|
||||||
|
@ -800,7 +818,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen)
|
hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := cc.newStream()
|
cs := cc.newStream()
|
||||||
|
@ -812,7 +830,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
endStream := !hasBody && !hasTrailers
|
endStream := !hasBody && !hasTrailers
|
||||||
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
|
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
|
||||||
cc.wmu.Unlock()
|
cc.wmu.Unlock()
|
||||||
traceWroteHeaders(cs.trace)
|
traceWroteHeaders(cs.trace)
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
@ -826,7 +844,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
// Don't bother sending a RST_STREAM (our write already failed;
|
// Don't bother sending a RST_STREAM (our write already failed;
|
||||||
// no need to keep writing)
|
// no need to keep writing)
|
||||||
traceWroteRequest(cs.trace, werr)
|
traceWroteRequest(cs.trace, werr)
|
||||||
return nil, werr
|
return nil, false, werr
|
||||||
}
|
}
|
||||||
|
|
||||||
var respHeaderTimer <-chan time.Time
|
var respHeaderTimer <-chan time.Time
|
||||||
|
@ -845,7 +863,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
bodyWritten := false
|
bodyWritten := false
|
||||||
ctx := reqContext(req)
|
ctx := reqContext(req)
|
||||||
|
|
||||||
handleReadLoopResponse := func(re resAndError) (*http.Response, error) {
|
handleReadLoopResponse := func(re resAndError) (*http.Response, bool, error) {
|
||||||
res := re.res
|
res := re.res
|
||||||
if re.err != nil || res.StatusCode > 299 {
|
if re.err != nil || res.StatusCode > 299 {
|
||||||
// On error or status code 3xx, 4xx, 5xx, etc abort any
|
// On error or status code 3xx, 4xx, 5xx, etc abort any
|
||||||
|
@ -861,18 +879,12 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWrite)
|
cs.abortRequestBodyWrite(errStopReqBodyWrite)
|
||||||
}
|
}
|
||||||
if re.err != nil {
|
if re.err != nil {
|
||||||
cc.mu.Lock()
|
|
||||||
afterBodyWrite := cs.startedWrite
|
|
||||||
cc.mu.Unlock()
|
|
||||||
cc.forgetStreamID(cs.ID)
|
cc.forgetStreamID(cs.ID)
|
||||||
if afterBodyWrite {
|
return nil, cs.getStartedWrite(), re.err
|
||||||
return nil, afterReqBodyWriteError{re.err}
|
|
||||||
}
|
|
||||||
return nil, re.err
|
|
||||||
}
|
}
|
||||||
res.Request = req
|
res.Request = req
|
||||||
res.TLS = cc.tlsState
|
res.TLS = cc.tlsState
|
||||||
return res, nil
|
return res, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -887,7 +899,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
cc.forgetStreamID(cs.ID)
|
cc.forgetStreamID(cs.ID)
|
||||||
return nil, errTimeout
|
return nil, cs.getStartedWrite(), errTimeout
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
if !hasBody || bodyWritten {
|
if !hasBody || bodyWritten {
|
||||||
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
|
@ -896,7 +908,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
cc.forgetStreamID(cs.ID)
|
cc.forgetStreamID(cs.ID)
|
||||||
return nil, ctx.Err()
|
return nil, cs.getStartedWrite(), ctx.Err()
|
||||||
case <-req.Cancel:
|
case <-req.Cancel:
|
||||||
if !hasBody || bodyWritten {
|
if !hasBody || bodyWritten {
|
||||||
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
|
@ -905,12 +917,12 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
cc.forgetStreamID(cs.ID)
|
cc.forgetStreamID(cs.ID)
|
||||||
return nil, errRequestCanceled
|
return nil, cs.getStartedWrite(), errRequestCanceled
|
||||||
case <-cs.peerReset:
|
case <-cs.peerReset:
|
||||||
// processResetStream already removed the
|
// processResetStream already removed the
|
||||||
// stream from the streams map; no need for
|
// stream from the streams map; no need for
|
||||||
// forgetStreamID.
|
// forgetStreamID.
|
||||||
return nil, cs.resetErr
|
return nil, cs.getStartedWrite(), cs.resetErr
|
||||||
case err := <-bodyWriter.resc:
|
case err := <-bodyWriter.resc:
|
||||||
// Prefer the read loop's response, if available. Issue 16102.
|
// Prefer the read loop's response, if available. Issue 16102.
|
||||||
select {
|
select {
|
||||||
|
@ -919,7 +931,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cs.getStartedWrite(), err
|
||||||
}
|
}
|
||||||
bodyWritten = true
|
bodyWritten = true
|
||||||
if d := cc.responseHeaderTimeout(); d != 0 {
|
if d := cc.responseHeaderTimeout(); d != 0 {
|
||||||
|
@ -971,13 +983,12 @@ func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires cc.wmu be held
|
// requires cc.wmu be held
|
||||||
func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, hdrs []byte) error {
|
func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, maxFrameSize int, hdrs []byte) error {
|
||||||
first := true // first frame written (HEADERS is first, then CONTINUATION)
|
first := true // first frame written (HEADERS is first, then CONTINUATION)
|
||||||
frameSize := int(cc.maxFrameSize)
|
|
||||||
for len(hdrs) > 0 && cc.werr == nil {
|
for len(hdrs) > 0 && cc.werr == nil {
|
||||||
chunk := hdrs
|
chunk := hdrs
|
||||||
if len(chunk) > frameSize {
|
if len(chunk) > maxFrameSize {
|
||||||
chunk = chunk[:frameSize]
|
chunk = chunk[:maxFrameSize]
|
||||||
}
|
}
|
||||||
hdrs = hdrs[len(chunk):]
|
hdrs = hdrs[len(chunk):]
|
||||||
endHeaders := len(hdrs) == 0
|
endHeaders := len(hdrs) == 0
|
||||||
|
@ -1085,17 +1096,26 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
|
||||||
var trls []byte
|
var trls []byte
|
||||||
if hasTrailers {
|
if hasTrailers {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
trls, err = cc.encodeTrailers(req)
|
||||||
trls = cc.encodeTrailers(req)
|
cc.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
cc.writeStreamReset(cs.ID, ErrCodeInternal, err)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cc.mu.Lock()
|
||||||
|
maxFrameSize := int(cc.maxFrameSize)
|
||||||
|
cc.mu.Unlock()
|
||||||
|
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
defer cc.wmu.Unlock()
|
defer cc.wmu.Unlock()
|
||||||
|
|
||||||
// Two ways to send END_STREAM: either with trailers, or
|
// Two ways to send END_STREAM: either with trailers, or
|
||||||
// with an empty DATA frame.
|
// with an empty DATA frame.
|
||||||
if len(trls) > 0 {
|
if len(trls) > 0 {
|
||||||
err = cc.writeHeaders(cs.ID, true, trls)
|
err = cc.writeHeaders(cs.ID, true, maxFrameSize, trls)
|
||||||
} else {
|
} else {
|
||||||
err = cc.fr.WriteData(cs.ID, true, nil)
|
err = cc.fr.WriteData(cs.ID, true, nil)
|
||||||
}
|
}
|
||||||
|
@ -1189,62 +1209,86 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8.1.2.3 Request Pseudo-Header Fields
|
enumerateHeaders := func(f func(name, value string)) {
|
||||||
// The :path pseudo-header field includes the path and query parts of the
|
// 8.1.2.3 Request Pseudo-Header Fields
|
||||||
// target URI (the path-absolute production and optionally a '?' character
|
// The :path pseudo-header field includes the path and query parts of the
|
||||||
// followed by the query production (see Sections 3.3 and 3.4 of
|
// target URI (the path-absolute production and optionally a '?' character
|
||||||
// [RFC3986]).
|
// followed by the query production (see Sections 3.3 and 3.4 of
|
||||||
cc.writeHeader(":authority", host)
|
// [RFC3986]).
|
||||||
cc.writeHeader(":method", req.Method)
|
f(":authority", host)
|
||||||
if req.Method != "CONNECT" {
|
f(":method", req.Method)
|
||||||
cc.writeHeader(":path", path)
|
if req.Method != "CONNECT" {
|
||||||
cc.writeHeader(":scheme", req.URL.Scheme)
|
f(":path", path)
|
||||||
}
|
f(":scheme", req.URL.Scheme)
|
||||||
if trailers != "" {
|
}
|
||||||
cc.writeHeader("trailer", trailers)
|
if trailers != "" {
|
||||||
|
f("trailer", trailers)
|
||||||
|
}
|
||||||
|
|
||||||
|
var didUA bool
|
||||||
|
for k, vv := range req.Header {
|
||||||
|
if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") {
|
||||||
|
// Host is :authority, already sent.
|
||||||
|
// Content-Length is automatic, set below.
|
||||||
|
continue
|
||||||
|
} else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") ||
|
||||||
|
strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") ||
|
||||||
|
strings.EqualFold(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 strings.EqualFold(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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vv {
|
||||||
|
f(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldSendReqContentLength(req.Method, contentLength) {
|
||||||
|
f("content-length", strconv.FormatInt(contentLength, 10))
|
||||||
|
}
|
||||||
|
if addGzipHeader {
|
||||||
|
f("accept-encoding", "gzip")
|
||||||
|
}
|
||||||
|
if !didUA {
|
||||||
|
f("user-agent", defaultUserAgent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var didUA bool
|
// Do a first pass over the headers counting bytes to ensure
|
||||||
for k, vv := range req.Header {
|
// we don't exceed cc.peerMaxHeaderListSize. This is done as a
|
||||||
lowKey := strings.ToLower(k)
|
// separate pass before encoding the headers to prevent
|
||||||
switch lowKey {
|
// modifying the hpack state.
|
||||||
case "host", "content-length":
|
hlSize := uint64(0)
|
||||||
// Host is :authority, already sent.
|
enumerateHeaders(func(name, value string) {
|
||||||
// Content-Length is automatic, set below.
|
hf := hpack.HeaderField{Name: name, Value: value}
|
||||||
continue
|
hlSize += uint64(hf.Size())
|
||||||
case "connection", "proxy-connection", "transfer-encoding", "upgrade", "keep-alive":
|
})
|
||||||
// Per 8.1.2.2 Connection-Specific Header
|
|
||||||
// Fields, don't send connection-specific
|
if hlSize > cc.peerMaxHeaderListSize {
|
||||||
// fields. We have already checked if any
|
return nil, errRequestHeaderListSize
|
||||||
// are error-worthy so just ignore the rest.
|
|
||||||
continue
|
|
||||||
case "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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v := range vv {
|
|
||||||
cc.writeHeader(lowKey, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if shouldSendReqContentLength(req.Method, contentLength) {
|
|
||||||
cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10))
|
|
||||||
}
|
|
||||||
if addGzipHeader {
|
|
||||||
cc.writeHeader("accept-encoding", "gzip")
|
|
||||||
}
|
|
||||||
if !didUA {
|
|
||||||
cc.writeHeader("user-agent", defaultUserAgent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header list size is ok. Write the headers.
|
||||||
|
enumerateHeaders(func(name, value string) {
|
||||||
|
cc.writeHeader(strings.ToLower(name), value)
|
||||||
|
})
|
||||||
|
|
||||||
return cc.hbuf.Bytes(), nil
|
return cc.hbuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1271,17 +1315,29 @@ func shouldSendReqContentLength(method string, contentLength int64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires cc.mu be held.
|
// requires cc.mu be held.
|
||||||
func (cc *ClientConn) encodeTrailers(req *http.Request) []byte {
|
func (cc *ClientConn) encodeTrailers(req *http.Request) ([]byte, error) {
|
||||||
cc.hbuf.Reset()
|
cc.hbuf.Reset()
|
||||||
|
|
||||||
|
hlSize := uint64(0)
|
||||||
for k, vv := range req.Trailer {
|
for k, vv := range req.Trailer {
|
||||||
// Transfer-Encoding, etc.. have already been filter at the
|
for _, v := range vv {
|
||||||
|
hf := hpack.HeaderField{Name: k, Value: v}
|
||||||
|
hlSize += uint64(hf.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hlSize > cc.peerMaxHeaderListSize {
|
||||||
|
return nil, errRequestHeaderListSize
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vv := range req.Trailer {
|
||||||
|
// Transfer-Encoding, etc.. have already been filtered at the
|
||||||
// start of RoundTrip
|
// start of RoundTrip
|
||||||
lowKey := strings.ToLower(k)
|
lowKey := strings.ToLower(k)
|
||||||
for _, v := range vv {
|
for _, v := range vv {
|
||||||
cc.writeHeader(lowKey, v)
|
cc.writeHeader(lowKey, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cc.hbuf.Bytes()
|
return cc.hbuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) writeHeader(name, value string) {
|
func (cc *ClientConn) writeHeader(name, value string) {
|
||||||
|
@ -1339,17 +1395,12 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
|
||||||
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
|
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
|
||||||
type clientConnReadLoop struct {
|
type clientConnReadLoop struct {
|
||||||
cc *ClientConn
|
cc *ClientConn
|
||||||
activeRes map[uint32]*clientStream // keyed by streamID
|
|
||||||
closeWhenIdle bool
|
closeWhenIdle bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLoop runs in its own goroutine and reads and dispatches frames.
|
// readLoop runs in its own goroutine and reads and dispatches frames.
|
||||||
func (cc *ClientConn) readLoop() {
|
func (cc *ClientConn) readLoop() {
|
||||||
rl := &clientConnReadLoop{
|
rl := &clientConnReadLoop{cc: cc}
|
||||||
cc: cc,
|
|
||||||
activeRes: make(map[uint32]*clientStream),
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rl.cleanup()
|
defer rl.cleanup()
|
||||||
cc.readerErr = rl.run()
|
cc.readerErr = rl.run()
|
||||||
if ce, ok := cc.readerErr.(ConnectionError); ok {
|
if ce, ok := cc.readerErr.(ConnectionError); ok {
|
||||||
|
@ -1404,10 +1455,8 @@ func (rl *clientConnReadLoop) cleanup() {
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
err = io.ErrUnexpectedEOF
|
err = io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for _, cs := range rl.activeRes {
|
|
||||||
cs.bufPipe.CloseWithError(err)
|
|
||||||
}
|
|
||||||
for _, cs := range cc.streams {
|
for _, cs := range cc.streams {
|
||||||
|
cs.bufPipe.CloseWithError(err) // no-op if already closed
|
||||||
select {
|
select {
|
||||||
case cs.resc <- resAndError{err: err}:
|
case cs.resc <- resAndError{err: err}:
|
||||||
default:
|
default:
|
||||||
|
@ -1485,7 +1534,7 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rl.closeWhenIdle && gotReply && maybeIdle && len(rl.activeRes) == 0 {
|
if rl.closeWhenIdle && gotReply && maybeIdle {
|
||||||
cc.closeIfIdle()
|
cc.closeIfIdle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1493,13 +1542,31 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
|
|
||||||
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
cc := rl.cc
|
cc := rl.cc
|
||||||
cs := cc.streamByID(f.StreamID, f.StreamEnded())
|
cs := cc.streamByID(f.StreamID, false)
|
||||||
if cs == nil {
|
if cs == nil {
|
||||||
// We'd get here if we canceled a request while the
|
// We'd get here if we canceled a request while the
|
||||||
// server had its response still in flight. So if this
|
// server had its response still in flight. So if this
|
||||||
// was just something we canceled, ignore it.
|
// was just something we canceled, ignore it.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if f.StreamEnded() {
|
||||||
|
// Issue 20521: If the stream has ended, streamByID() causes
|
||||||
|
// clientStream.done to be closed, which causes the request's bodyWriter
|
||||||
|
// to be closed with an errStreamClosed, which may be received by
|
||||||
|
// clientConn.RoundTrip before the result of processing these headers.
|
||||||
|
// Deferring stream closure allows the header processing to occur first.
|
||||||
|
// clientConn.RoundTrip may still receive the bodyWriter error first, but
|
||||||
|
// the fix for issue 16102 prioritises any response.
|
||||||
|
//
|
||||||
|
// Issue 22413: If there is no request body, we should close the
|
||||||
|
// stream before writing to cs.resc so that the stream is closed
|
||||||
|
// immediately once RoundTrip returns.
|
||||||
|
if cs.req.Body != nil {
|
||||||
|
defer cc.forgetStreamID(f.StreamID)
|
||||||
|
} else {
|
||||||
|
cc.forgetStreamID(f.StreamID)
|
||||||
|
}
|
||||||
|
}
|
||||||
if !cs.firstByte {
|
if !cs.firstByte {
|
||||||
if cs.trace != nil {
|
if cs.trace != nil {
|
||||||
// TODO(bradfitz): move first response byte earlier,
|
// TODO(bradfitz): move first response byte earlier,
|
||||||
|
@ -1523,6 +1590,7 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
}
|
}
|
||||||
// Any other error type is a stream error.
|
// Any other error type is a stream error.
|
||||||
cs.cc.writeStreamReset(f.StreamID, ErrCodeProtocol, err)
|
cs.cc.writeStreamReset(f.StreamID, ErrCodeProtocol, err)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
cs.resc <- resAndError{err: err}
|
cs.resc <- resAndError{err: err}
|
||||||
return nil // return nil from process* funcs to keep conn alive
|
return nil // return nil from process* funcs to keep conn alive
|
||||||
}
|
}
|
||||||
|
@ -1530,9 +1598,6 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
// (nil, nil) special case. See handleResponse docs.
|
// (nil, nil) special case. See handleResponse docs.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if res.Body != noBody {
|
|
||||||
rl.activeRes[cs.ID] = cs
|
|
||||||
}
|
|
||||||
cs.resTrailer = &res.Trailer
|
cs.resTrailer = &res.Trailer
|
||||||
cs.resc <- resAndError{res: res}
|
cs.resc <- resAndError{res: res}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1552,11 +1617,11 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
|
||||||
|
|
||||||
status := f.PseudoValue("status")
|
status := f.PseudoValue("status")
|
||||||
if status == "" {
|
if status == "" {
|
||||||
return nil, errors.New("missing status pseudo header")
|
return nil, errors.New("malformed response from server: missing status pseudo header")
|
||||||
}
|
}
|
||||||
statusCode, err := strconv.Atoi(status)
|
statusCode, err := strconv.Atoi(status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("malformed non-numeric status pseudo header")
|
return nil, errors.New("malformed response from server: malformed non-numeric status pseudo header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusCode == 100 {
|
if statusCode == 100 {
|
||||||
|
@ -1789,7 +1854,23 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if !cs.firstByte {
|
||||||
|
cc.logf("protocol error: received DATA before a HEADERS frame")
|
||||||
|
rl.endStreamError(cs, StreamError{
|
||||||
|
StreamID: f.StreamID,
|
||||||
|
Code: ErrCodeProtocol,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if f.Length > 0 {
|
if f.Length > 0 {
|
||||||
|
if cs.req.Method == "HEAD" && len(data) > 0 {
|
||||||
|
cc.logf("protocol error: received DATA on a HEAD request")
|
||||||
|
rl.endStreamError(cs, StreamError{
|
||||||
|
StreamID: f.StreamID,
|
||||||
|
Code: ErrCodeProtocol,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// Check connection-level flow control.
|
// Check connection-level flow control.
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
if cs.inflow.available() >= int32(f.Length) {
|
if cs.inflow.available() >= int32(f.Length) {
|
||||||
|
@ -1851,11 +1932,10 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
code = cs.copyTrailers
|
code = cs.copyTrailers
|
||||||
}
|
}
|
||||||
cs.bufPipe.closeWithErrorAndCode(err, code)
|
|
||||||
delete(rl.activeRes, cs.ID)
|
|
||||||
if isConnectionCloseRequest(cs.req) {
|
if isConnectionCloseRequest(cs.req) {
|
||||||
rl.closeWhenIdle = true
|
rl.closeWhenIdle = true
|
||||||
}
|
}
|
||||||
|
cs.bufPipe.closeWithErrorAndCode(err, code)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case cs.resc <- resAndError{err: err}:
|
case cs.resc <- resAndError{err: err}:
|
||||||
|
@ -1903,6 +1983,8 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
|
||||||
cc.maxFrameSize = s.Val
|
cc.maxFrameSize = s.Val
|
||||||
case SettingMaxConcurrentStreams:
|
case SettingMaxConcurrentStreams:
|
||||||
cc.maxConcurrentStreams = s.Val
|
cc.maxConcurrentStreams = s.Val
|
||||||
|
case SettingMaxHeaderListSize:
|
||||||
|
cc.peerMaxHeaderListSize = uint64(s.Val)
|
||||||
case SettingInitialWindowSize:
|
case SettingInitialWindowSize:
|
||||||
// Values above the maximum flow-control
|
// Values above the maximum flow-control
|
||||||
// window size of 2^31-1 MUST be treated as a
|
// window size of 2^31-1 MUST be treated as a
|
||||||
|
@ -1980,7 +2062,6 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
|
||||||
cs.bufPipe.CloseWithError(err)
|
cs.bufPipe.CloseWithError(err)
|
||||||
cs.cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
|
cs.cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
|
||||||
}
|
}
|
||||||
delete(rl.activeRes, cs.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2069,6 +2150,7 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
|
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
|
||||||
|
errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit")
|
||||||
errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
|
errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2/hpack"
|
"golang.org/x/net/http2/hpack"
|
||||||
"golang.org/x/net/lex/httplex"
|
"golang.org/x/net/lex/httplex"
|
||||||
|
@ -90,11 +89,7 @@ type writeGoAway struct {
|
||||||
|
|
||||||
func (p *writeGoAway) writeFrame(ctx writeContext) error {
|
func (p *writeGoAway) writeFrame(ctx writeContext) error {
|
||||||
err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
|
err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
|
||||||
if p.code != 0 {
|
ctx.Flush() // ignore error: we're hanging up on them anyway
|
||||||
ctx.Flush() // ignore error: we're hanging up on them anyway
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
ctx.CloseConn()
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/secure/bidirule"
|
"golang.org/x/text/secure/bidirule"
|
||||||
|
"golang.org/x/text/unicode/bidi"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ func VerifyDNSLength(verify bool) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||||
// dots, such as U+3002, are removed as well.
|
// 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
|
// This is the behavior suggested by the UTS #46 and is adopted by some
|
||||||
// browsers.
|
// browsers.
|
||||||
|
@ -92,7 +93,7 @@ func ValidateLabels(enable bool) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrictDomainName limits the set of permissable ASCII characters to those
|
// 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
|
// 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.
|
||||||
//
|
//
|
||||||
|
@ -142,7 +143,6 @@ func MapForLookup() Option {
|
||||||
o.mapping = validateAndMap
|
o.mapping = validateAndMap
|
||||||
StrictDomainName(true)(o)
|
StrictDomainName(true)(o)
|
||||||
ValidateLabels(true)(o)
|
ValidateLabels(true)(o)
|
||||||
RemoveLeadingDots(true)(o)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,14 +160,14 @@ type options struct {
|
||||||
|
|
||||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
// mapping implements a validation and mapping step as defined in RFC 5895
|
||||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
// or UTS 46, tailored to, for example, domain registration or lookup.
|
||||||
mapping func(p *Profile, s string) (string, error)
|
mapping func(p *Profile, s string) (mapped string, isBidi bool, err error)
|
||||||
|
|
||||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
||||||
// defined in RFC 5893.
|
// defined in RFC 5893.
|
||||||
bidirule func(s string) bool
|
bidirule func(s string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Profile defines the configuration of a IDNA mapper.
|
// A Profile defines the configuration of an IDNA mapper.
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
@ -251,23 +251,21 @@ var (
|
||||||
|
|
||||||
punycode = &Profile{}
|
punycode = &Profile{}
|
||||||
lookup = &Profile{options{
|
lookup = &Profile{options{
|
||||||
transitional: true,
|
transitional: true,
|
||||||
useSTD3Rules: true,
|
useSTD3Rules: true,
|
||||||
validateLabels: true,
|
validateLabels: true,
|
||||||
removeLeadingDots: true,
|
trie: trie,
|
||||||
trie: trie,
|
fromPuny: validateFromPunycode,
|
||||||
fromPuny: validateFromPunycode,
|
mapping: validateAndMap,
|
||||||
mapping: validateAndMap,
|
bidirule: bidirule.ValidString,
|
||||||
bidirule: bidirule.ValidString,
|
|
||||||
}}
|
}}
|
||||||
display = &Profile{options{
|
display = &Profile{options{
|
||||||
useSTD3Rules: true,
|
useSTD3Rules: true,
|
||||||
validateLabels: true,
|
validateLabels: true,
|
||||||
removeLeadingDots: true,
|
trie: trie,
|
||||||
trie: trie,
|
fromPuny: validateFromPunycode,
|
||||||
fromPuny: validateFromPunycode,
|
mapping: validateAndMap,
|
||||||
mapping: validateAndMap,
|
bidirule: bidirule.ValidString,
|
||||||
bidirule: bidirule.ValidString,
|
|
||||||
}}
|
}}
|
||||||
registration = &Profile{options{
|
registration = &Profile{options{
|
||||||
useSTD3Rules: true,
|
useSTD3Rules: true,
|
||||||
|
@ -302,14 +300,16 @@ func (e runeError) Error() string {
|
||||||
// see http://www.unicode.org/reports/tr46.
|
// see http://www.unicode.org/reports/tr46.
|
||||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
|
var isBidi bool
|
||||||
if p.mapping != nil {
|
if p.mapping != nil {
|
||||||
s, err = p.mapping(p, s)
|
s, isBidi, err = p.mapping(p, s)
|
||||||
}
|
}
|
||||||
// Remove leading empty labels.
|
// Remove leading empty labels.
|
||||||
if p.removeLeadingDots {
|
if p.removeLeadingDots {
|
||||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: allow for a quick check of the tables data.
|
||||||
// It seems like we should only create this error on ToASCII, but the
|
// It seems like we should only create this error on ToASCII, but the
|
||||||
// UTS 46 conformance tests suggests we should always check this.
|
// UTS 46 conformance tests suggests we should always check this.
|
||||||
if err == nil && p.verifyDNSLength && s == "" {
|
if err == nil && p.verifyDNSLength && s == "" {
|
||||||
|
@ -335,6 +335,7 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
// Spec says keep the old label.
|
// Spec says keep the old label.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
||||||
labels.set(u)
|
labels.set(u)
|
||||||
if err == nil && p.validateLabels {
|
if err == nil && p.validateLabels {
|
||||||
err = p.fromPuny(p, u)
|
err = p.fromPuny(p, u)
|
||||||
|
@ -349,6 +350,14 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
err = p.validateLabel(label)
|
err = p.validateLabel(label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isBidi && p.bidirule != nil && err == nil {
|
||||||
|
for labels.reset(); !labels.done(); labels.next() {
|
||||||
|
if !p.bidirule(labels.label()) {
|
||||||
|
err = &labelError{s, "B"}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if toASCII {
|
if toASCII {
|
||||||
for labels.reset(); !labels.done(); labels.next() {
|
for labels.reset(); !labels.done(); labels.next() {
|
||||||
label := labels.label()
|
label := labels.label()
|
||||||
|
@ -380,16 +389,26 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalize(p *Profile, s string) (string, error) {
|
func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
|
||||||
return norm.NFC.String(s), nil
|
// TODO: consider first doing a quick check to see if any of these checks
|
||||||
|
// need to be done. This will make it slower in the general case, but
|
||||||
|
// faster in the common case.
|
||||||
|
mapped = norm.NFC.String(s)
|
||||||
|
isBidi = bidirule.DirectionString(mapped) == bidi.RightToLeft
|
||||||
|
return mapped, isBidi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRegistration(p *Profile, s string) (string, error) {
|
func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
|
||||||
|
// TODO: filter need for normalization in loop below.
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return s, &labelError{s, "V1"}
|
return s, false, &labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
return s, bidi, runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
|
bidi = bidi || info(v).isBidi(s[i:])
|
||||||
// Copy bytes not copied so far.
|
// Copy bytes not copied so far.
|
||||||
switch p.simplify(info(v).category()) {
|
switch p.simplify(info(v).category()) {
|
||||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||||
|
@ -397,21 +416,50 @@ func validateRegistration(p *Profile, s string) (string, error) {
|
||||||
case valid, deviation:
|
case valid, deviation:
|
||||||
case disallowed, mapped, unknown, ignored:
|
case disallowed, mapped, unknown, ignored:
|
||||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
r, _ := utf8.DecodeRuneInString(s[i:])
|
||||||
return s, runeError(r)
|
return s, bidi, runeError(r)
|
||||||
}
|
}
|
||||||
i += sz
|
i += sz
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, bidi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndMap(p *Profile, s string) (string, error) {
|
func (c info) isBidi(s string) bool {
|
||||||
|
if !c.isMapped() {
|
||||||
|
return c&attributesMask == rtl
|
||||||
|
}
|
||||||
|
// TODO: also store bidi info for mapped data. This is possible, but a bit
|
||||||
|
// cumbersome and not for the common case.
|
||||||
|
p, _ := bidi.LookupString(s)
|
||||||
|
switch p.Class() {
|
||||||
|
case bidi.R, bidi.AL, bidi.AN:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
||||||
var (
|
var (
|
||||||
err error
|
b []byte
|
||||||
b []byte
|
k int
|
||||||
k int
|
|
||||||
)
|
)
|
||||||
|
// combinedInfoBits contains the or-ed bits of all runes. We use this
|
||||||
|
// to derive the mayNeedNorm bit later. This may trigger normalization
|
||||||
|
// overeagerly, but it will not do so in the common case. The end result
|
||||||
|
// is another 10% saving on BenchmarkProfile for the common case.
|
||||||
|
var combinedInfoBits info
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
b = append(b, s[k:i]...)
|
||||||
|
b = append(b, "\ufffd"...)
|
||||||
|
k = len(s)
|
||||||
|
if err == nil {
|
||||||
|
err = runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
combinedInfoBits |= info(v)
|
||||||
|
bidi = bidi || info(v).isBidi(s[i:])
|
||||||
start := i
|
start := i
|
||||||
i += sz
|
i += sz
|
||||||
// Copy bytes not copied so far.
|
// Copy bytes not copied so far.
|
||||||
|
@ -438,7 +486,9 @@ func validateAndMap(p *Profile, s string) (string, error) {
|
||||||
}
|
}
|
||||||
if k == 0 {
|
if k == 0 {
|
||||||
// No changes so far.
|
// No changes so far.
|
||||||
s = norm.NFC.String(s)
|
if combinedInfoBits&mayNeedNorm != 0 {
|
||||||
|
s = norm.NFC.String(s)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
b = append(b, s[k:]...)
|
b = append(b, s[k:]...)
|
||||||
if norm.NFC.QuickSpan(b) != len(b) {
|
if norm.NFC.QuickSpan(b) != len(b) {
|
||||||
|
@ -447,7 +497,7 @@ func validateAndMap(p *Profile, s string) (string, error) {
|
||||||
// TODO: the punycode converters require strings as input.
|
// TODO: the punycode converters require strings as input.
|
||||||
s = string(b)
|
s = string(b)
|
||||||
}
|
}
|
||||||
return s, err
|
return s, bidi, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// A labelIter allows iterating over domain name labels.
|
// A labelIter allows iterating over domain name labels.
|
||||||
|
@ -542,8 +592,13 @@ func validateFromPunycode(p *Profile, s string) error {
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return &labelError{s, "V1"}
|
return &labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
|
// TODO: detect whether string may have to be normalized in the following
|
||||||
|
// loop.
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
return runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
||||||
return &labelError{s, "V6"}
|
return &labelError{s, "V6"}
|
||||||
}
|
}
|
||||||
|
@ -616,16 +671,13 @@ var joinStates = [][numJoinTypes]joinState{
|
||||||
|
|
||||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||||
// already implicitly satisfied by the overall implementation.
|
// already implicitly satisfied by the overall implementation.
|
||||||
func (p *Profile) validateLabel(s string) error {
|
func (p *Profile) validateLabel(s string) (err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
if p.verifyDNSLength {
|
if p.verifyDNSLength {
|
||||||
return &labelError{s, "A4"}
|
return &labelError{s, "A4"}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.bidirule != nil && !p.bidirule(s) {
|
|
||||||
return &labelError{s, "B"}
|
|
||||||
}
|
|
||||||
if !p.validateLabels {
|
if !p.validateLabels {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,9 +26,9 @@ package idna
|
||||||
// 15..3 index into xor or mapping table
|
// 15..3 index into xor or mapping table
|
||||||
// }
|
// }
|
||||||
// } else {
|
// } else {
|
||||||
// 15..13 unused
|
// 15..14 unused
|
||||||
// 12 modifier (including virama)
|
// 13 mayNeedNorm
|
||||||
// 11 virama modifier
|
// 12..11 attributes
|
||||||
// 10..8 joining type
|
// 10..8 joining type
|
||||||
// 7..3 category type
|
// 7..3 category type
|
||||||
// }
|
// }
|
||||||
|
@ -49,15 +49,20 @@ const (
|
||||||
joinShift = 8
|
joinShift = 8
|
||||||
joinMask = 0x07
|
joinMask = 0x07
|
||||||
|
|
||||||
viramaModifier = 0x0800
|
// Attributes
|
||||||
|
attributesMask = 0x1800
|
||||||
|
viramaModifier = 0x1800
|
||||||
modifier = 0x1000
|
modifier = 0x1000
|
||||||
|
rtl = 0x0800
|
||||||
|
|
||||||
|
mayNeedNorm = 0x2000
|
||||||
)
|
)
|
||||||
|
|
||||||
// A category corresponds to a category defined in the IDNA mapping table.
|
// A category corresponds to a category defined in the IDNA mapping table.
|
||||||
type category uint16
|
type category uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
unknown category = 0 // not defined currently in unicode.
|
unknown category = 0 // not currently defined in unicode.
|
||||||
mapped category = 1
|
mapped category = 1
|
||||||
disallowedSTD3Mapped category = 2
|
disallowedSTD3Mapped category = 2
|
||||||
deviation category = 3
|
deviation category = 3
|
||||||
|
@ -110,5 +115,5 @@ func (c info) isModifier() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c info) isViramaModifier() bool {
|
func (c info) isViramaModifier() bool {
|
||||||
return c&(viramaModifier|catSmallMask) == viramaModifier
|
return c&(attributesMask|catSmallMask) == viramaModifier
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (p *PerHost) dialerForRequest(host string) Dialer {
|
||||||
return p.bypass
|
return p.bypass
|
||||||
}
|
}
|
||||||
if host == zone[1:] {
|
if host == zone[1:] {
|
||||||
// For a zone "example.com", we match "example.com"
|
// For a zone ".example.com", we match "example.com"
|
||||||
// too.
|
// too.
|
||||||
return p.bypass
|
return p.bypass
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
// with an optional username and password. See RFC 1928.
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||||
s := &socks5{
|
s := &socks5{
|
||||||
network: network,
|
network: network,
|
||||||
|
@ -60,7 +60,7 @@ var socks5Errors = []string{
|
||||||
"address type not supported",
|
"address type not supported",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp", "tcp6", "tcp4":
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
@ -120,6 +120,7 @@ func (s *socks5) connect(conn net.Conn, target string) error {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
if buf[1] == socks5AuthPassword {
|
if buf[1] == socks5AuthPassword {
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
buf = append(buf, 1 /* password protocol version */)
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
|
Loading…
Reference in New Issue