Update go dependencies

This commit is contained in:
Manuel de Brito Fontes 2018-05-26 11:27:53 -04:00 committed by Manuel Alejandro de Brito Fontes
parent 15ffb51394
commit bb4d483837
No known key found for this signature in database
GPG key ID: 786136016A8BA02A
1621 changed files with 86368 additions and 284392 deletions

View file

@ -4,16 +4,15 @@ Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
@ -23,9 +22,5 @@ The gophers there will answer or ask you to file an issue if you've tripped over
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
**We do not accept GitHub pull requests**
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.

View file

@ -171,9 +171,16 @@ func Verify(publicKey PublicKey, message, sig []byte) bool {
edwards25519.ScReduce(&hReduced, &digest)
var R edwards25519.ProjectiveGroupElement
var b [32]byte
copy(b[:], sig[32:])
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
var s [32]byte
copy(s[:], sig[32:])
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
// the range [0, order) in order to prevent signature malleability.
if !edwards25519.ScMinimal(&s) {
return false
}
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
var checkR [32]byte
R.ToBytes(&checkR)

View file

@ -1,183 +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.
package ed25519
import (
"bufio"
"bytes"
"compress/gzip"
"crypto"
"crypto/rand"
"encoding/hex"
"os"
"strings"
"testing"
"golang.org/x/crypto/ed25519/internal/edwards25519"
)
type zeroReader struct{}
func (zeroReader) Read(buf []byte) (int, error) {
for i := range buf {
buf[i] = 0
}
return len(buf), nil
}
func TestUnmarshalMarshal(t *testing.T) {
pub, _, _ := GenerateKey(rand.Reader)
var A edwards25519.ExtendedGroupElement
var pubBytes [32]byte
copy(pubBytes[:], pub)
if !A.FromBytes(&pubBytes) {
t.Fatalf("ExtendedGroupElement.FromBytes failed")
}
var pub2 [32]byte
A.ToBytes(&pub2)
if pubBytes != pub2 {
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2)
}
}
func TestSignVerify(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
message := []byte("test message")
sig := Sign(private, message)
if !Verify(public, message, sig) {
t.Errorf("valid signature rejected")
}
wrongMessage := []byte("wrong message")
if Verify(public, wrongMessage, sig) {
t.Errorf("signature of different message accepted")
}
}
func TestCryptoSigner(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
signer := crypto.Signer(private)
publicInterface := signer.Public()
public2, ok := publicInterface.(PublicKey)
if !ok {
t.Fatalf("expected PublicKey from Public() but got %T", publicInterface)
}
if !bytes.Equal(public, public2) {
t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2)
}
message := []byte("message")
var noHash crypto.Hash
signature, err := signer.Sign(zero, message, noHash)
if err != nil {
t.Fatalf("error from Sign(): %s", err)
}
if !Verify(public, message, signature) {
t.Errorf("Verify failed on signature from Sign()")
}
}
func TestGolden(t *testing.T) {
// sign.input.gz is a selection of test cases from
// https://ed25519.cr.yp.to/python/sign.input
testDataZ, err := os.Open("testdata/sign.input.gz")
if err != nil {
t.Fatal(err)
}
defer testDataZ.Close()
testData, err := gzip.NewReader(testDataZ)
if err != nil {
t.Fatal(err)
}
defer testData.Close()
scanner := bufio.NewScanner(testData)
lineNo := 0
for scanner.Scan() {
lineNo++
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) != 5 {
t.Fatalf("bad number of parts on line %d", lineNo)
}
privBytes, _ := hex.DecodeString(parts[0])
pubKey, _ := hex.DecodeString(parts[1])
msg, _ := hex.DecodeString(parts[2])
sig, _ := hex.DecodeString(parts[3])
// The signatures in the test vectors also include the message
// at the end, but we just want R and S.
sig = sig[:SignatureSize]
if l := len(pubKey); l != PublicKeySize {
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
}
var priv [PrivateKeySize]byte
copy(priv[:], privBytes)
copy(priv[32:], pubKey)
sig2 := Sign(priv[:], msg)
if !bytes.Equal(sig, sig2[:]) {
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
}
if !Verify(pubKey, msg, sig2) {
t.Errorf("signature failed to verify on line %d", lineNo)
}
}
if err := scanner.Err(); err != nil {
t.Fatalf("error reading test data: %s", err)
}
}
func BenchmarkKeyGeneration(b *testing.B) {
var zero zeroReader
for i := 0; i < b.N; i++ {
if _, _, err := GenerateKey(zero); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSigning(b *testing.B) {
var zero zeroReader
_, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sign(priv, message)
}
}
func BenchmarkVerification(b *testing.B) {
var zero zeroReader
pub, priv, err := GenerateKey(zero)
if err != nil {
b.Fatal(err)
}
message := []byte("Hello, world!")
signature := Sign(priv, message)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Verify(pub, message, signature)
}
}

View file

@ -4,6 +4,8 @@
package edwards25519
import "encoding/binary"
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
@ -1769,3 +1771,23 @@ func ScReduce(out *[32]byte, s *[64]byte) {
out[30] = byte(s11 >> 9)
out[31] = byte(s11 >> 17)
}
// order is the order of Curve25519 in little-endian form.
var order = [4]uint64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0, 0x1000000000000000}
// ScMinimal returns true if the given scalar is less than the order of the
// curve.
func ScMinimal(scalar *[32]byte) bool {
for i := 3; ; i-- {
v := binary.LittleEndian.Uint64(scalar[i*8:])
if v > order[i] {
return false
} else if v < order[i] {
break
} else if i == 0 {
return false
}
}
return true
}

View file

@ -1,122 +0,0 @@
// Copyright 2013 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 ssh
import (
"errors"
"io"
"net"
"testing"
)
type server struct {
*ServerConn
chans <-chan NewChannel
}
func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
sconn, chans, reqs, err := NewServerConn(c, conf)
if err != nil {
return nil, err
}
go DiscardRequests(reqs)
return &server{sconn, chans}, nil
}
func (s *server) Accept() (NewChannel, error) {
n, ok := <-s.chans
if !ok {
return nil, io.EOF
}
return n, nil
}
func sshPipe() (Conn, *server, error) {
c1, c2, err := netPipe()
if err != nil {
return nil, nil, err
}
clientConf := ClientConfig{
User: "user",
}
serverConf := ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["ecdsa"])
done := make(chan *server, 1)
go func() {
server, err := newServer(c2, &serverConf)
if err != nil {
done <- nil
}
done <- server
}()
client, _, reqs, err := NewClientConn(c1, "", &clientConf)
if err != nil {
return nil, nil, err
}
server := <-done
if server == nil {
return nil, nil, errors.New("server handshake failed.")
}
go DiscardRequests(reqs)
return client, server, nil
}
func BenchmarkEndToEnd(b *testing.B) {
b.StopTimer()
client, server, err := sshPipe()
if err != nil {
b.Fatalf("sshPipe: %v", err)
}
defer client.Close()
defer server.Close()
size := (1 << 20)
input := make([]byte, size)
output := make([]byte, size)
b.SetBytes(int64(size))
done := make(chan int, 1)
go func() {
newCh, err := server.Accept()
if err != nil {
b.Fatalf("Client: %v", err)
}
ch, incoming, err := newCh.Accept()
go DiscardRequests(incoming)
for i := 0; i < b.N; i++ {
if _, err := io.ReadFull(ch, output); err != nil {
b.Fatalf("ReadFull: %v", err)
}
}
ch.Close()
done <- 1
}()
ch, in, err := client.OpenChannel("speed", nil)
if err != nil {
b.Fatalf("OpenChannel: %v", err)
}
go DiscardRequests(in)
b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
if _, err := ch.Write(input); err != nil {
b.Fatalf("WriteFull: %v", err)
}
}
ch.Close()
b.StopTimer()
<-done
}

View file

@ -1,87 +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.
package ssh
import (
"io"
"testing"
)
var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
func TestBufferReadwrite(t *testing.T) {
b := newBuffer()
b.write(alphabet[:10])
r, _ := b.Read(make([]byte, 10))
if r != 10 {
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
}
b = newBuffer()
b.write(alphabet[:5])
r, _ = b.Read(make([]byte, 10))
if r != 5 {
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
}
b = newBuffer()
b.write(alphabet[:10])
r, _ = b.Read(make([]byte, 5))
if r != 5 {
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
}
b = newBuffer()
b.write(alphabet[:5])
b.write(alphabet[5:15])
r, _ = b.Read(make([]byte, 10))
r2, _ := b.Read(make([]byte, 10))
if r != 10 || r2 != 5 || 15 != r+r2 {
t.Fatal("Expected written == read == 15")
}
}
func TestBufferClose(t *testing.T) {
b := newBuffer()
b.write(alphabet[:10])
b.eof()
_, err := b.Read(make([]byte, 5))
if err != nil {
t.Fatal("expected read of 5 to not return EOF")
}
b = newBuffer()
b.write(alphabet[:10])
b.eof()
r, err := b.Read(make([]byte, 5))
r2, err2 := b.Read(make([]byte, 10))
if r != 5 || r2 != 5 || err != nil || err2 != nil {
t.Fatal("expected reads of 5 and 5")
}
b = newBuffer()
b.write(alphabet[:10])
b.eof()
r, err = b.Read(make([]byte, 5))
r2, err2 = b.Read(make([]byte, 10))
r3, err3 := b.Read(make([]byte, 10))
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
}
b = newBuffer()
b.write(make([]byte, 5))
b.write(make([]byte, 10))
b.eof()
r, err = b.Read(make([]byte, 9))
r2, err2 = b.Read(make([]byte, 3))
r3, err3 = b.Read(make([]byte, 3))
r4, err4 := b.Read(make([]byte, 10))
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
}
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
}
}

View file

@ -44,7 +44,9 @@ type Signature struct {
const CertTimeInfinity = 1<<64 - 1
// An Certificate represents an OpenSSH certificate as defined in
// [PROTOCOL.certkeys]?rev=1.8.
// [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the
// PublicKey interface, so it can be unmarshaled using
// ParsePublicKey.
type Certificate struct {
Nonce []byte
Key PublicKey
@ -340,10 +342,10 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
// the signature of the certificate.
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
if c.IsRevoked != nil && c.IsRevoked(cert) {
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
}
for opt, _ := range cert.CriticalOptions {
for opt := range cert.CriticalOptions {
// sourceAddressCriticalOption will be enforced by
// serverAuthenticate
if opt == sourceAddressCriticalOption {

View file

@ -1,222 +0,0 @@
// Copyright 2013 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 ssh
import (
"bytes"
"crypto/rand"
"reflect"
"testing"
"time"
)
// Cert generated by ssh-keygen 6.0p1 Debian-4.
// % ssh-keygen -s ca-key -I test user-key
const exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=`
func TestParseCert(t *testing.T) {
authKeyBytes := []byte(exampleSSHCert)
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
if err != nil {
t.Fatalf("ParseAuthorizedKey: %v", err)
}
if len(rest) > 0 {
t.Errorf("rest: got %q, want empty", rest)
}
if _, ok := key.(*Certificate); !ok {
t.Fatalf("got %v (%T), want *Certificate", key, key)
}
marshaled := MarshalAuthorizedKey(key)
// Before comparison, remove the trailing newline that
// MarshalAuthorizedKey adds.
marshaled = marshaled[:len(marshaled)-1]
if !bytes.Equal(authKeyBytes, marshaled) {
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
}
}
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
// Critical Options:
// force-command /bin/sleep
// source-address 192.168.1.0/24
// Extensions:
// permit-X11-forwarding
// permit-agent-forwarding
// permit-port-forwarding
// permit-pty
// permit-user-rc
const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ`
func TestParseCertWithOptions(t *testing.T) {
opts := map[string]string{
"source-address": "192.168.1.0/24",
"force-command": "/bin/sleep",
}
exts := map[string]string{
"permit-X11-forwarding": "",
"permit-agent-forwarding": "",
"permit-port-forwarding": "",
"permit-pty": "",
"permit-user-rc": "",
}
authKeyBytes := []byte(exampleSSHCertWithOptions)
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
if err != nil {
t.Fatalf("ParseAuthorizedKey: %v", err)
}
if len(rest) > 0 {
t.Errorf("rest: got %q, want empty", rest)
}
cert, ok := key.(*Certificate)
if !ok {
t.Fatalf("got %v (%T), want *Certificate", key, key)
}
if !reflect.DeepEqual(cert.CriticalOptions, opts) {
t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts)
}
if !reflect.DeepEqual(cert.Extensions, exts) {
t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts)
}
marshaled := MarshalAuthorizedKey(key)
// Before comparison, remove the trailing newline that
// MarshalAuthorizedKey adds.
marshaled = marshaled[:len(marshaled)-1]
if !bytes.Equal(authKeyBytes, marshaled) {
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
}
}
func TestValidateCert(t *testing.T) {
key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert))
if err != nil {
t.Fatalf("ParseAuthorizedKey: %v", err)
}
validCert, ok := key.(*Certificate)
if !ok {
t.Fatalf("got %v (%T), want *Certificate", key, key)
}
checker := CertChecker{}
checker.IsUserAuthority = func(k PublicKey) bool {
return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
}
if err := checker.CheckCert("user", validCert); err != nil {
t.Errorf("Unable to validate certificate: %v", err)
}
invalidCert := &Certificate{
Key: testPublicKeys["rsa"],
SignatureKey: testPublicKeys["ecdsa"],
ValidBefore: CertTimeInfinity,
Signature: &Signature{},
}
if err := checker.CheckCert("user", invalidCert); err == nil {
t.Error("Invalid cert signature passed validation")
}
}
func TestValidateCertTime(t *testing.T) {
cert := Certificate{
ValidPrincipals: []string{"user"},
Key: testPublicKeys["rsa"],
ValidAfter: 50,
ValidBefore: 100,
}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
for ts, ok := range map[int64]bool{
25: false,
50: true,
99: true,
100: false,
125: false,
} {
checker := CertChecker{
Clock: func() time.Time { return time.Unix(ts, 0) },
}
checker.IsUserAuthority = func(k PublicKey) bool {
return bytes.Equal(k.Marshal(),
testPublicKeys["ecdsa"].Marshal())
}
if v := checker.CheckCert("user", &cert); (v == nil) != ok {
t.Errorf("Authenticate(%d): %v", ts, v)
}
}
}
// TODO(hanwen): tests for
//
// host keys:
// * fallbacks
func TestHostKeyCert(t *testing.T) {
cert := &Certificate{
ValidPrincipals: []string{"hostname", "hostname.domain", "otherhost"},
Key: testPublicKeys["rsa"],
ValidBefore: CertTimeInfinity,
CertType: HostCert,
}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
checker := &CertChecker{
IsHostAuthority: func(p PublicKey, addr string) bool {
return addr == "hostname:22" && bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
},
}
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
if err != nil {
t.Errorf("NewCertSigner: %v", err)
}
for _, test := range []struct {
addr string
succeed bool
}{
{addr: "hostname:22", succeed: true},
{addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22'
{addr: "lasthost:22", succeed: false},
} {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
errc := make(chan error)
go func() {
conf := ServerConfig{
NoClientAuth: true,
}
conf.AddHostKey(certSigner)
_, _, _, err := NewServerConn(c1, &conf)
errc <- err
}()
config := &ClientConfig{
User: "user",
HostKeyCallback: checker.CheckHostKey,
}
_, _, _, err = NewClientConn(c2, test.addr, config)
if (err == nil) != test.succeed {
t.Fatalf("NewClientConn(%q): %v", test.addr, err)
}
err = <-errc
if (err == nil) != test.succeed {
t.Fatalf("NewServerConn(%q): %v", test.addr, err)
}
}
}

View file

@ -205,32 +205,32 @@ type channel struct {
// writePacket sends a packet. If the packet is a channel close, it updates
// sentClose. This method takes the lock c.writeMu.
func (c *channel) writePacket(packet []byte) error {
c.writeMu.Lock()
if c.sentClose {
c.writeMu.Unlock()
func (ch *channel) writePacket(packet []byte) error {
ch.writeMu.Lock()
if ch.sentClose {
ch.writeMu.Unlock()
return io.EOF
}
c.sentClose = (packet[0] == msgChannelClose)
err := c.mux.conn.writePacket(packet)
c.writeMu.Unlock()
ch.sentClose = (packet[0] == msgChannelClose)
err := ch.mux.conn.writePacket(packet)
ch.writeMu.Unlock()
return err
}
func (c *channel) sendMessage(msg interface{}) error {
func (ch *channel) sendMessage(msg interface{}) error {
if debugMux {
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg)
}
p := Marshal(msg)
binary.BigEndian.PutUint32(p[1:], c.remoteId)
return c.writePacket(p)
binary.BigEndian.PutUint32(p[1:], ch.remoteId)
return ch.writePacket(p)
}
// WriteExtended writes data to a specific extended stream. These streams are
// used, for example, for stderr.
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
if c.sentEOF {
func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
if ch.sentEOF {
return 0, io.EOF
}
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
@ -241,16 +241,16 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
opCode = msgChannelExtendedData
}
c.writeMu.Lock()
packet := c.packetPool[extendedCode]
ch.writeMu.Lock()
packet := ch.packetPool[extendedCode]
// We don't remove the buffer from packetPool, so
// WriteExtended calls from different goroutines will be
// flagged as errors by the race detector.
c.writeMu.Unlock()
ch.writeMu.Unlock()
for len(data) > 0 {
space := min(c.maxRemotePayload, len(data))
if space, err = c.remoteWin.reserve(space); err != nil {
space := min(ch.maxRemotePayload, len(data))
if space, err = ch.remoteWin.reserve(space); err != nil {
return n, err
}
if want := headerLength + space; uint32(cap(packet)) < want {
@ -262,13 +262,13 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
todo := data[:space]
packet[0] = opCode
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
binary.BigEndian.PutUint32(packet[1:], ch.remoteId)
if extendedCode > 0 {
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
}
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
copy(packet[headerLength:], todo)
if err = c.writePacket(packet); err != nil {
if err = ch.writePacket(packet); err != nil {
return n, err
}
@ -276,14 +276,14 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
data = data[len(todo):]
}
c.writeMu.Lock()
c.packetPool[extendedCode] = packet
c.writeMu.Unlock()
ch.writeMu.Lock()
ch.packetPool[extendedCode] = packet
ch.writeMu.Unlock()
return n, err
}
func (c *channel) handleData(packet []byte) error {
func (ch *channel) handleData(packet []byte) error {
headerLen := 9
isExtendedData := packet[0] == msgChannelExtendedData
if isExtendedData {
@ -303,7 +303,7 @@ func (c *channel) handleData(packet []byte) error {
if length == 0 {
return nil
}
if length > c.maxIncomingPayload {
if length > ch.maxIncomingPayload {
// TODO(hanwen): should send Disconnect?
return errors.New("ssh: incoming packet exceeds maximum payload size")
}
@ -313,21 +313,21 @@ func (c *channel) handleData(packet []byte) error {
return errors.New("ssh: wrong packet length")
}
c.windowMu.Lock()
if c.myWindow < length {
c.windowMu.Unlock()
ch.windowMu.Lock()
if ch.myWindow < length {
ch.windowMu.Unlock()
// TODO(hanwen): should send Disconnect with reason?
return errors.New("ssh: remote side wrote too much")
}
c.myWindow -= length
c.windowMu.Unlock()
ch.myWindow -= length
ch.windowMu.Unlock()
if extended == 1 {
c.extPending.write(data)
ch.extPending.write(data)
} else if extended > 0 {
// discard other extended data.
} else {
c.pending.write(data)
ch.pending.write(data)
}
return nil
}
@ -384,31 +384,31 @@ func (c *channel) close() {
// responseMessageReceived is called when a success or failure message is
// received on a channel to check that such a message is reasonable for the
// given channel.
func (c *channel) responseMessageReceived() error {
if c.direction == channelInbound {
func (ch *channel) responseMessageReceived() error {
if ch.direction == channelInbound {
return errors.New("ssh: channel response message received on inbound channel")
}
if c.decided {
if ch.decided {
return errors.New("ssh: duplicate response received for channel")
}
c.decided = true
ch.decided = true
return nil
}
func (c *channel) handlePacket(packet []byte) error {
func (ch *channel) handlePacket(packet []byte) error {
switch packet[0] {
case msgChannelData, msgChannelExtendedData:
return c.handleData(packet)
return ch.handleData(packet)
case msgChannelClose:
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
c.mux.chanList.remove(c.localId)
c.close()
ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId})
ch.mux.chanList.remove(ch.localId)
ch.close()
return nil
case msgChannelEOF:
// RFC 4254 is mute on how EOF affects dataExt messages but
// it is logical to signal EOF at the same time.
c.extPending.eof()
c.pending.eof()
ch.extPending.eof()
ch.pending.eof()
return nil
}
@ -419,24 +419,24 @@ func (c *channel) handlePacket(packet []byte) error {
switch msg := decoded.(type) {
case *channelOpenFailureMsg:
if err := c.responseMessageReceived(); err != nil {
if err := ch.responseMessageReceived(); err != nil {
return err
}
c.mux.chanList.remove(msg.PeersId)
c.msg <- msg
ch.mux.chanList.remove(msg.PeersID)
ch.msg <- msg
case *channelOpenConfirmMsg:
if err := c.responseMessageReceived(); err != nil {
if err := ch.responseMessageReceived(); err != nil {
return err
}
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
}
c.remoteId = msg.MyId
c.maxRemotePayload = msg.MaxPacketSize
c.remoteWin.add(msg.MyWindow)
c.msg <- msg
ch.remoteId = msg.MyID
ch.maxRemotePayload = msg.MaxPacketSize
ch.remoteWin.add(msg.MyWindow)
ch.msg <- msg
case *windowAdjustMsg:
if !c.remoteWin.add(msg.AdditionalBytes) {
if !ch.remoteWin.add(msg.AdditionalBytes) {
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
}
case *channelRequestMsg:
@ -444,12 +444,12 @@ func (c *channel) handlePacket(packet []byte) error {
Type: msg.Request,
WantReply: msg.WantReply,
Payload: msg.RequestSpecificData,
ch: c,
ch: ch,
}
c.incomingRequests <- &req
ch.incomingRequests <- &req
default:
c.msg <- msg
ch.msg <- msg
}
return nil
}
@ -488,23 +488,23 @@ func (e *extChannel) Read(data []byte) (n int, err error) {
return e.ch.ReadExtended(data, e.code)
}
func (c *channel) Accept() (Channel, <-chan *Request, error) {
if c.decided {
func (ch *channel) Accept() (Channel, <-chan *Request, error) {
if ch.decided {
return nil, nil, errDecidedAlready
}
c.maxIncomingPayload = channelMaxPacket
ch.maxIncomingPayload = channelMaxPacket
confirm := channelOpenConfirmMsg{
PeersId: c.remoteId,
MyId: c.localId,
MyWindow: c.myWindow,
MaxPacketSize: c.maxIncomingPayload,
PeersID: ch.remoteId,
MyID: ch.localId,
MyWindow: ch.myWindow,
MaxPacketSize: ch.maxIncomingPayload,
}
c.decided = true
if err := c.sendMessage(confirm); err != nil {
ch.decided = true
if err := ch.sendMessage(confirm); err != nil {
return nil, nil, err
}
return c, c.incomingRequests, nil
return ch, ch.incomingRequests, nil
}
func (ch *channel) Reject(reason RejectionReason, message string) error {
@ -512,7 +512,7 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
return errDecidedAlready
}
reject := channelOpenFailureMsg{
PeersId: ch.remoteId,
PeersID: ch.remoteId,
Reason: reason,
Message: message,
Language: "en",
@ -541,7 +541,7 @@ func (ch *channel) CloseWrite() error {
}
ch.sentEOF = true
return ch.sendMessage(channelEOFMsg{
PeersId: ch.remoteId})
PeersID: ch.remoteId})
}
func (ch *channel) Close() error {
@ -550,7 +550,7 @@ func (ch *channel) Close() error {
}
return ch.sendMessage(channelCloseMsg{
PeersId: ch.remoteId})
PeersID: ch.remoteId})
}
// Extended returns an io.ReadWriter that sends and receives data on the given,
@ -577,7 +577,7 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
}
msg := channelRequestMsg{
PeersId: ch.remoteId,
PeersID: ch.remoteId,
Request: name,
WantReply: wantReply,
RequestSpecificData: payload,
@ -614,11 +614,11 @@ func (ch *channel) ackRequest(ok bool) error {
var msg interface{}
if !ok {
msg = channelRequestFailureMsg{
PeersId: ch.remoteId,
PeersID: ch.remoteId,
}
} else {
msg = channelRequestSuccessMsg{
PeersId: ch.remoteId,
PeersID: ch.remoteId,
}
}
return ch.sendMessage(msg)

View file

@ -16,6 +16,10 @@ import (
"hash"
"io"
"io/ioutil"
"math/bits"
"golang.org/x/crypto/internal/chacha20"
"golang.org/x/crypto/poly1305"
)
const (
@ -53,78 +57,78 @@ func newRC4(key, iv []byte) (cipher.Stream, error) {
return rc4.NewCipher(key)
}
type streamCipherMode struct {
keySize int
ivSize int
skip int
createFunc func(key, iv []byte) (cipher.Stream, error)
type cipherMode struct {
keySize int
ivSize int
create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error)
}
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
if len(key) < c.keySize {
panic("ssh: key length too small for cipher")
}
if len(iv) < c.ivSize {
panic("ssh: iv too small for cipher")
}
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
if err != nil {
return nil, err
}
var streamDump []byte
if c.skip > 0 {
streamDump = make([]byte, 512)
}
for remainingToDump := c.skip; remainingToDump > 0; {
dumpThisTime := remainingToDump
if dumpThisTime > len(streamDump) {
dumpThisTime = len(streamDump)
func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
return func(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
stream, err := createFunc(key, iv)
if err != nil {
return nil, err
}
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
remainingToDump -= dumpThisTime
}
return stream, nil
var streamDump []byte
if skip > 0 {
streamDump = make([]byte, 512)
}
for remainingToDump := skip; remainingToDump > 0; {
dumpThisTime := remainingToDump
if dumpThisTime > len(streamDump) {
dumpThisTime = len(streamDump)
}
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
remainingToDump -= dumpThisTime
}
mac := macModes[algs.MAC].new(macKey)
return &streamPacketCipher{
mac: mac,
etm: macModes[algs.MAC].etm,
macResult: make([]byte, mac.Size()),
cipher: stream,
}, nil
}
}
// cipherModes documents properties of supported ciphers. Ciphers not included
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*streamCipherMode{
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, 1536, newRC4},
"arcfour256": {32, 0, 1536, newRC4},
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
// RC4) has problems with weak keys, and should be used with caution."
// RFC4345 introduces improved versions of Arcfour.
"arcfour": {16, 0, 0, newRC4},
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
// AES-GCM is not a stream cipher, so it is constructed with a
// special case. If we add any more non-stream ciphers, we
// should invest a cleaner way to do this.
gcmCipherID: {16, 12, 0, nil},
// AEAD ciphers
gcmCipherID: {16, 12, newGCMCipher},
chacha20Poly1305ID: {64, 0, newChaCha20Cipher},
// CBC mode is insecure and so is not included in the default config.
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
// needed, it's possible to specify a custom Config to enable it.
// You should expect that an active attacker can recover plaintext if
// you do.
aes128cbcID: {16, aes.BlockSize, 0, nil},
aes128cbcID: {16, aes.BlockSize, newAESCBCCipher},
// 3des-cbc is insecure and is disabled by default.
tripledescbcID: {24, des.BlockSize, 0, nil},
// 3des-cbc is insecure and is not included in the default
// config.
tripledescbcID: {24, des.BlockSize, newTripleDESCBCCipher},
}
// prefixLen is the length of the packet prefix that contains the packet length
@ -304,7 +308,7 @@ type gcmCipher struct {
buf []byte
}
func newGCMCipher(iv, key []byte) (packetCipher, error) {
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
@ -372,7 +376,7 @@ func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
}
length := binary.BigEndian.Uint32(c.prefix[:])
if length > maxPacket {
return nil, errors.New("ssh: max packet length exceeded.")
return nil, errors.New("ssh: max packet length exceeded")
}
if cap(c.buf) < int(length+gcmTagSize) {
@ -422,7 +426,7 @@ type cbcCipher struct {
oracleCamouflage uint32
}
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
cbc := &cbcCipher{
mac: macModes[algs.MAC].new(macKey),
decrypter: cipher.NewCBCDecrypter(c, iv),
@ -436,13 +440,13 @@ func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorith
return cbc, nil
}
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
if err != nil {
return nil, err
}
@ -450,13 +454,13 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi
return cbc, nil
}
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
c, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
if err != nil {
return nil, err
}
@ -548,11 +552,11 @@ func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error)
c.packetData = c.packetData[:entirePacketSize]
}
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
n, err := io.ReadFull(r, c.packetData[firstBlockLength:])
if err != nil {
return nil, err
} else {
c.oracleCamouflage -= uint32(n)
}
c.oracleCamouflage -= uint32(n)
remainingCrypted := c.packetData[firstBlockLength:macStart]
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
@ -627,3 +631,140 @@ func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, pack
return nil
}
const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com
// AEAD, which is described here:
//
// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
//
// the methods here also implement padding, which RFC4253 Section 6
// also requires of stream ciphers.
type chacha20Poly1305Cipher struct {
lengthKey [8]uint32
contentKey [8]uint32
buf []byte
}
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
if len(key) != 64 {
panic(len(key))
}
c := &chacha20Poly1305Cipher{
buf: make([]byte, 256),
}
for i := range c.contentKey {
c.contentKey[i] = binary.LittleEndian.Uint32(key[i*4 : (i+1)*4])
}
for i := range c.lengthKey {
c.lengthKey[i] = binary.LittleEndian.Uint32(key[(i+8)*4 : (i+9)*4])
}
return c, nil
}
func (c *chacha20Poly1305Cipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
nonce := [3]uint32{0, 0, bits.ReverseBytes32(seqNum)}
s := chacha20.New(c.contentKey, nonce)
var polyKey [32]byte
s.XORKeyStream(polyKey[:], polyKey[:])
s.Advance() // skip next 32 bytes
encryptedLength := c.buf[:4]
if _, err := io.ReadFull(r, encryptedLength); err != nil {
return nil, err
}
var lenBytes [4]byte
chacha20.New(c.lengthKey, nonce).XORKeyStream(lenBytes[:], encryptedLength)
length := binary.BigEndian.Uint32(lenBytes[:])
if length > maxPacket {
return nil, errors.New("ssh: invalid packet length, packet too large")
}
contentEnd := 4 + length
packetEnd := contentEnd + poly1305.TagSize
if uint32(cap(c.buf)) < packetEnd {
c.buf = make([]byte, packetEnd)
copy(c.buf[:], encryptedLength)
} else {
c.buf = c.buf[:packetEnd]
}
if _, err := io.ReadFull(r, c.buf[4:packetEnd]); err != nil {
return nil, err
}
var mac [poly1305.TagSize]byte
copy(mac[:], c.buf[contentEnd:packetEnd])
if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) {
return nil, errors.New("ssh: MAC failure")
}
plain := c.buf[4:contentEnd]
s.XORKeyStream(plain, plain)
padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
// the maximum size, which is 255.
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
}
if int(padding)+1 >= len(plain) {
return nil, fmt.Errorf("ssh: padding %d too large", padding)
}
plain = plain[1 : len(plain)-int(padding)]
return plain, nil
}
func (c *chacha20Poly1305Cipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error {
nonce := [3]uint32{0, 0, bits.ReverseBytes32(seqNum)}
s := chacha20.New(c.contentKey, nonce)
var polyKey [32]byte
s.XORKeyStream(polyKey[:], polyKey[:])
s.Advance() // skip next 32 bytes
// There is no blocksize, so fall back to multiple of 8 byte
// padding, as described in RFC 4253, Sec 6.
const packetSizeMultiple = 8
padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple
if padding < 4 {
padding += packetSizeMultiple
}
// size (4 bytes), padding (1), payload, padding, tag.
totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize
if cap(c.buf) < totalLength {
c.buf = make([]byte, totalLength)
} else {
c.buf = c.buf[:totalLength]
}
binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding))
chacha20.New(c.lengthKey, nonce).XORKeyStream(c.buf, c.buf[:4])
c.buf[4] = byte(padding)
copy(c.buf[5:], payload)
packetEnd := 5 + len(payload) + padding
if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil {
return err
}
s.XORKeyStream(c.buf[4:], c.buf[4:packetEnd])
var mac [poly1305.TagSize]byte
poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey)
copy(c.buf[packetEnd:], mac[:])
if _, err := w.Write(c.buf); err != nil {
return err
}
return nil
}

View file

@ -1,129 +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.
package ssh
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/rand"
"testing"
)
func TestDefaultCiphersExist(t *testing.T) {
for _, cipherAlgo := range supportedCiphers {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("default cipher %q is unknown", cipherAlgo)
}
}
}
func TestPacketCiphers(t *testing.T) {
// Still test aes128cbc cipher although it's commented out.
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
defer delete(cipherModes, aes128cbcID)
for cipher := range cipherModes {
for mac := range macModes {
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: cipher,
MAC: mac,
Compression: "none",
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
continue
}
server, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
continue
}
want := "bla bla"
input := []byte(want)
buf := &bytes.Buffer{}
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
t.Errorf("writePacket(%q, %q): %v", cipher, mac, err)
continue
}
packet, err := server.readPacket(0, buf)
if err != nil {
t.Errorf("readPacket(%q, %q): %v", cipher, mac, err)
continue
}
if string(packet) != want {
t.Errorf("roundtrip(%q, %q): got %q, want %q", cipher, mac, packet, want)
}
}
}
}
func TestCBCOracleCounterMeasure(t *testing.T) {
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
defer delete(cipherModes, aes128cbcID)
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: aes128cbcID,
MAC: "hmac-sha1",
Compression: "none",
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client): %v", err)
}
want := "bla bla"
input := []byte(want)
buf := &bytes.Buffer{}
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
t.Errorf("writePacket: %v", err)
}
packetSize := buf.Len()
buf.Write(make([]byte, 2*maxPacket))
// We corrupt each byte, but this usually will only test the
// 'packet too large' or 'MAC failure' cases.
lastRead := -1
for i := 0; i < packetSize; i++ {
server, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client): %v", err)
}
fresh := &bytes.Buffer{}
fresh.Write(buf.Bytes())
fresh.Bytes()[i] ^= 0x01
before := fresh.Len()
_, err = server.readPacket(0, fresh)
if err == nil {
t.Errorf("corrupt byte %d: readPacket succeeded ", i)
continue
}
if _, ok := err.(cbcError); !ok {
t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err)
continue
}
after := fresh.Len()
bytesRead := before - after
if bytesRead < maxPacket {
t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket)
continue
}
if i > 0 && bytesRead != lastRead {
t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead)
}
lastRead = bytesRead
}
}

View file

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"net"
"os"
"sync"
"time"
)
@ -18,6 +19,8 @@ import (
type Client struct {
Conn
handleForwardsOnce sync.Once // guards calling (*Client).handleForwards
forwards forwardList // forwarded tcpip connections from the remote side
mu sync.Mutex
channelHandlers map[string]chan NewChannel
@ -59,8 +62,6 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn.Wait()
conn.forwards.closeAll()
}()
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
return conn
}
@ -187,6 +188,10 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
// net.Conn underlying the the SSH connection.
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
// BannerCallback is the function type used for treat the banner sent by
// the server. A BannerCallback receives the message sent by the remote server.
type BannerCallback func(message string) error
// A ClientConfig structure is used to configure a Client. It must not be
// modified after having been passed to an SSH function.
type ClientConfig struct {
@ -209,6 +214,12 @@ type ClientConfig struct {
// FixedHostKey can be used for simplistic host key checks.
HostKeyCallback HostKeyCallback
// BannerCallback is called during the SSH dance to display a custom
// server's message. The client configuration can supply this callback to
// handle it as wished. The function BannerDisplayStderr can be used for
// simplistic display on Stderr.
BannerCallback BannerCallback
// ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used.
ClientVersion string
@ -255,3 +266,13 @@ func FixedHostKey(key PublicKey) HostKeyCallback {
hk := &fixedHostKey{key}
return hk.check
}
// BannerDisplayStderr returns a function that can be used for
// ClientConfig.BannerCallback to display banners on os.Stderr.
func BannerDisplayStderr() BannerCallback {
return func(banner string) error {
_, err := os.Stderr.WriteString(banner)
return err
}
}

View file

@ -11,6 +11,14 @@ import (
"io"
)
type authResult int
const (
authFailure authResult = iota
authPartialSuccess
authSuccess
)
// clientAuthenticate authenticates with the remote server. See RFC 4252.
func (c *connection) clientAuthenticate(config *ClientConfig) error {
// initiate user auth session
@ -37,11 +45,12 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
if err != nil {
return err
}
if ok {
if ok == authSuccess {
// success
return nil
} else if ok == authFailure {
tried[auth.method()] = true
}
tried[auth.method()] = true
if methods == nil {
methods = lastMethods
}
@ -82,7 +91,7 @@ type AuthMethod interface {
// If authentication is not successful, a []string of alternative
// method names is returned. If the slice is nil, it will be ignored
// and the previous set of possible methods will be reused.
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
// method returns the RFC 4252 method name.
method() string
@ -91,13 +100,13 @@ type AuthMethod interface {
// "none" authentication, RFC 4252 section 5.2.
type noneAuth int
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
if err := c.writePacket(Marshal(&userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: "none",
})); err != nil {
return false, nil, err
return authFailure, nil, err
}
return handleAuthResponse(c)
@ -111,7 +120,7 @@ func (n *noneAuth) method() string {
// a function call, e.g. by prompting the user.
type passwordCallback func() (password string, err error)
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
type passwordAuthMsg struct {
User string `sshtype:"50"`
Service string
@ -125,7 +134,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
// The program may only find out that the user doesn't have a password
// when prompting.
if err != nil {
return false, nil, err
return authFailure, nil, err
}
if err := c.writePacket(Marshal(&passwordAuthMsg{
@ -135,7 +144,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
Reply: false,
Password: pw,
})); err != nil {
return false, nil, err
return authFailure, nil, err
}
return handleAuthResponse(c)
@ -178,7 +187,7 @@ func (cb publicKeyCallback) method() string {
return "publickey"
}
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
// Authentication is performed by sending an enquiry to test if a key is
// acceptable to the remote. If the key is acceptable, the client will
// attempt to authenticate with the valid key. If not the client will repeat
@ -186,13 +195,13 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
signers, err := cb()
if err != nil {
return false, nil, err
return authFailure, nil, err
}
var methods []string
for _, signer := range signers {
ok, err := validateKey(signer.PublicKey(), user, c)
if err != nil {
return false, nil, err
return authFailure, nil, err
}
if !ok {
continue
@ -206,7 +215,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
Method: cb.method(),
}, []byte(pub.Type()), pubKey))
if err != nil {
return false, nil, err
return authFailure, nil, err
}
// manually wrap the serialized signature in a string
@ -224,24 +233,24 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
}
p := Marshal(&msg)
if err := c.writePacket(p); err != nil {
return false, nil, err
return authFailure, nil, err
}
var success bool
var success authResult
success, methods, err = handleAuthResponse(c)
if err != nil {
return false, nil, err
return authFailure, nil, err
}
// If authentication succeeds or the list of available methods does not
// contain the "publickey" method, do not attempt to authenticate with any
// other keys. According to RFC 4252 Section 7, the latter can occur when
// additional authentication methods are required.
if success || !containsMethod(methods, cb.method()) {
if success == authSuccess || !containsMethod(methods, cb.method()) {
return success, methods, err
}
}
return false, methods, nil
return authFailure, methods, nil
}
func containsMethod(methods []string, method string) bool {
@ -283,7 +292,9 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
}
switch packet[0] {
case msgUserAuthBanner:
// TODO(gpaul): add callback to present the banner to the user
if err := handleBannerResponse(c, packet); err != nil {
return false, err
}
case msgUserAuthPubKeyOk:
var msg userAuthPubKeyOkMsg
if err := Unmarshal(packet, &msg); err != nil {
@ -316,30 +327,53 @@ func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMet
// handleAuthResponse returns whether the preceding authentication request succeeded
// along with a list of remaining authentication methods to try next and
// an error if an unexpected response was received.
func handleAuthResponse(c packetConn) (bool, []string, error) {
func handleAuthResponse(c packetConn) (authResult, []string, error) {
for {
packet, err := c.readPacket()
if err != nil {
return false, nil, err
return authFailure, nil, err
}
switch packet[0] {
case msgUserAuthBanner:
// TODO: add callback to present the banner to the user
if err := handleBannerResponse(c, packet); err != nil {
return authFailure, nil, err
}
case msgUserAuthFailure:
var msg userAuthFailureMsg
if err := Unmarshal(packet, &msg); err != nil {
return false, nil, err
return authFailure, nil, err
}
return false, msg.Methods, nil
if msg.PartialSuccess {
return authPartialSuccess, msg.Methods, nil
}
return authFailure, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
return authSuccess, nil, nil
default:
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
}
}
}
func handleBannerResponse(c packetConn, packet []byte) error {
var msg userAuthBannerMsg
if err := Unmarshal(packet, &msg); err != nil {
return err
}
transport, ok := c.(*handshakeTransport)
if !ok {
return nil
}
if transport.bannerCallback != nil {
return transport.bannerCallback(msg.Message)
}
return nil
}
// KeyboardInteractiveChallenge should print questions, optionally
// disabling echoing (e.g. for passwords), and return all the answers.
// Challenge may be called multiple times in a single session. After
@ -359,7 +393,7 @@ func (cb KeyboardInteractiveChallenge) method() string {
return "keyboard-interactive"
}
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
type initiateMsg struct {
User string `sshtype:"50"`
Service string
@ -373,37 +407,42 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
Service: serviceSSH,
Method: "keyboard-interactive",
})); err != nil {
return false, nil, err
return authFailure, nil, err
}
for {
packet, err := c.readPacket()
if err != nil {
return false, nil, err
return authFailure, nil, err
}
// like handleAuthResponse, but with less options.
switch packet[0] {
case msgUserAuthBanner:
// TODO: Print banners during userauth.
if err := handleBannerResponse(c, packet); err != nil {
return authFailure, nil, err
}
continue
case msgUserAuthInfoRequest:
// OK
case msgUserAuthFailure:
var msg userAuthFailureMsg
if err := Unmarshal(packet, &msg); err != nil {
return false, nil, err
return authFailure, nil, err
}
return false, msg.Methods, nil
if msg.PartialSuccess {
return authPartialSuccess, msg.Methods, nil
}
return authFailure, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
return authSuccess, nil, nil
default:
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
}
var msg userAuthInfoRequestMsg
if err := Unmarshal(packet, &msg); err != nil {
return false, nil, err
return authFailure, nil, err
}
// Manually unpack the prompt/echo pairs.
@ -413,7 +452,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
for i := 0; i < int(msg.NumPrompts); i++ {
prompt, r, ok := parseString(rest)
if !ok || len(r) == 0 {
return false, nil, errors.New("ssh: prompt format error")
return authFailure, nil, errors.New("ssh: prompt format error")
}
prompts = append(prompts, string(prompt))
echos = append(echos, r[0] != 0)
@ -421,16 +460,16 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
}
if len(rest) != 0 {
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
}
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
if err != nil {
return false, nil, err
return authFailure, nil, err
}
if len(answers) != len(prompts) {
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
}
responseLength := 1 + 4
for _, a := range answers {
@ -446,7 +485,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
}
if err := c.writePacket(serialized); err != nil {
return false, nil, err
return authFailure, nil, err
}
}
}
@ -456,10 +495,10 @@ type retryableAuthMethod struct {
maxTries int
}
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
ok, methods, err = r.authMethod.auth(session, user, c, rand)
if ok || err != nil { // either success or error terminate
if ok != authFailure || err != nil { // either success, partial success or error terminate
return ok, methods, err
}
}

View file

@ -1,628 +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.
package ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"os"
"strings"
"testing"
)
type keyboardInteractive map[string]string
func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
var answers []string
for _, q := range questions {
answers = append(answers, cr[q])
}
return answers, nil
}
// reused internally by tests
var clientPassword = "tiger"
// tryAuth runs a handshake with a given config against an SSH server
// with config serverConfig
func tryAuth(t *testing.T, config *ClientConfig) error {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
certChecker := CertChecker{
IsUserAuthority: func(k PublicKey) bool {
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
},
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
return nil, nil
}
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
},
IsRevoked: func(c *Certificate) bool {
return c.Serial == 666
},
}
serverConfig := &ServerConfig{
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
if conn.User() == "testuser" && string(pass) == clientPassword {
return nil, nil
}
return nil, errors.New("password auth failed")
},
PublicKeyCallback: certChecker.Authenticate,
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
ans, err := challenge("user",
"instruction",
[]string{"question1", "question2"},
[]bool{true, true})
if err != nil {
return nil, err
}
ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
if ok {
challenge("user", "motd", nil, nil)
return nil, nil
}
return nil, errors.New("keyboard-interactive failed")
},
}
serverConfig.AddHostKey(testSigners["rsa"])
go newServer(c1, serverConfig)
_, _, _, err = NewClientConn(c2, "", config)
return err
}
func TestClientAuthPublicKey(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
func TestAuthMethodPassword(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
Password(clientPassword),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
func TestAuthMethodFallback(t *testing.T) {
var passwordCalled bool
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(testSigners["rsa"]),
PasswordCallback(
func() (string, error) {
passwordCalled = true
return "WRONG", nil
}),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
if passwordCalled {
t.Errorf("password auth tried before public-key auth.")
}
}
func TestAuthMethodWrongPassword(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
Password("wrong"),
PublicKeys(testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
func TestAuthMethodKeyboardInteractive(t *testing.T) {
answers := keyboardInteractive(map[string]string{
"question1": "answer1",
"question2": "answer2",
})
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
KeyboardInteractive(answers.Challenge),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
func TestAuthMethodWrongKeyboardInteractive(t *testing.T) {
answers := keyboardInteractive(map[string]string{
"question1": "answer1",
"question2": "WRONG",
})
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
KeyboardInteractive(answers.Challenge),
},
}
if err := tryAuth(t, config); err == nil {
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
}
}
// the mock server will only authenticate ssh-rsa keys
func TestAuthMethodInvalidPublicKey(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(testSigners["dsa"]),
},
}
if err := tryAuth(t, config); err == nil {
t.Fatalf("dsa private key should not have authenticated with rsa public key")
}
}
// the client should authenticate with the second key
func TestAuthMethodRSAandDSA(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(testSigners["dsa"], testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("client could not authenticate with rsa key: %v", err)
}
}
func TestClientHMAC(t *testing.T) {
for _, mac := range supportedMACs {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(testSigners["rsa"]),
},
Config: Config{
MACs: []string{mac},
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
}
}
}
// issue 4285.
func TestClientUnsupportedCipher(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(),
},
Config: Config{
Ciphers: []string{"aes128-cbc"}, // not currently supported
},
}
if err := tryAuth(t, config); err == nil {
t.Errorf("expected no ciphers in common")
}
}
func TestClientUnsupportedKex(t *testing.T) {
if os.Getenv("GO_BUILDER_NAME") != "" {
t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198")
}
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(),
},
Config: Config{
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
t.Errorf("got %v, expected 'common algorithm'", err)
}
}
func TestClientLoginCert(t *testing.T) {
cert := &Certificate{
Key: testPublicKeys["rsa"],
ValidBefore: CertTimeInfinity,
CertType: UserCert,
}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
if err != nil {
t.Fatalf("NewCertSigner: %v", err)
}
clientConfig := &ClientConfig{
User: "user",
HostKeyCallback: InsecureIgnoreHostKey(),
}
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
// should succeed
if err := tryAuth(t, clientConfig); err != nil {
t.Errorf("cert login failed: %v", err)
}
// corrupted signature
cert.Signature.Blob[0]++
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with corrupted sig")
}
// revoked
cert.Serial = 666
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("revoked cert login succeeded")
}
cert.Serial = 1
// sign with wrong key
cert.SignCert(rand.Reader, testSigners["dsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with non-authoritative key")
}
// host cert
cert.CertType = HostCert
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with wrong type")
}
cert.CertType = UserCert
// principal specified
cert.ValidPrincipals = []string{"user"}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err != nil {
t.Errorf("cert login failed: %v", err)
}
// wrong principal specified
cert.ValidPrincipals = []string{"fred"}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with wrong principal")
}
cert.ValidPrincipals = nil
// added critical option
cert.CriticalOptions = map[string]string{"root-access": "yes"}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login passed with unrecognized critical option")
}
// allowed source address
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err != nil {
t.Errorf("cert login with source-address failed: %v", err)
}
// disallowed source address
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::42"}
cert.SignCert(rand.Reader, testSigners["ecdsa"])
if err := tryAuth(t, clientConfig); err == nil {
t.Errorf("cert login with source-address succeeded")
}
}
func testPermissionsPassing(withPermissions bool, t *testing.T) {
serverConfig := &ServerConfig{
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
if conn.User() == "nopermissions" {
return nil, nil
}
return &Permissions{}, nil
},
}
serverConfig.AddHostKey(testSigners["rsa"])
clientConfig := &ClientConfig{
Auth: []AuthMethod{
PublicKeys(testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if withPermissions {
clientConfig.User = "permissions"
} else {
clientConfig.User = "nopermissions"
}
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewClientConn(c2, "", clientConfig)
serverConn, err := newServer(c1, serverConfig)
if err != nil {
t.Fatal(err)
}
if p := serverConn.Permissions; (p != nil) != withPermissions {
t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p)
}
}
func TestPermissionsPassing(t *testing.T) {
testPermissionsPassing(true, t)
}
func TestNoPermissionsPassing(t *testing.T) {
testPermissionsPassing(false, t)
}
func TestRetryableAuth(t *testing.T) {
n := 0
passwords := []string{"WRONG1", "WRONG2"}
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
RetryableAuthMethod(PasswordCallback(func() (string, error) {
p := passwords[n]
n++
return p, nil
}), 2),
PublicKeys(testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
if n != 2 {
t.Fatalf("Did not try all passwords")
}
}
func ExampleRetryableAuthMethod(t *testing.T) {
user := "testuser"
NumberOfPrompts := 3
// Normally this would be a callback that prompts the user to answer the
// provided questions
Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
return []string{"answer1", "answer2"}, nil
}
config := &ClientConfig{
HostKeyCallback: InsecureIgnoreHostKey(),
User: user,
Auth: []AuthMethod{
RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
},
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
}
// Test if username is received on server side when NoClientAuth is used
func TestClientAuthNone(t *testing.T) {
user := "testuser"
serverConfig := &ServerConfig{
NoClientAuth: true,
}
serverConfig.AddHostKey(testSigners["rsa"])
clientConfig := &ClientConfig{
User: user,
HostKeyCallback: InsecureIgnoreHostKey(),
}
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewClientConn(c2, "", clientConfig)
serverConn, err := newServer(c1, serverConfig)
if err != nil {
t.Fatalf("newServer: %v", err)
}
if serverConn.User() != user {
t.Fatalf("server: got %q, want %q", serverConn.User(), user)
}
}
// Test if authentication attempts are limited on server when MaxAuthTries is set
func TestClientAuthMaxAuthTries(t *testing.T) {
user := "testuser"
serverConfig := &ServerConfig{
MaxAuthTries: 2,
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
if conn.User() == "testuser" && string(pass) == "right" {
return nil, nil
}
return nil, errors.New("password auth failed")
},
}
serverConfig.AddHostKey(testSigners["rsa"])
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
Reason: 2,
Message: "too many authentication failures",
})
for tries := 2; tries < 4; tries++ {
n := tries
clientConfig := &ClientConfig{
User: user,
Auth: []AuthMethod{
RetryableAuthMethod(PasswordCallback(func() (string, error) {
n--
if n == 0 {
return "right", nil
}
return "wrong", nil
}), tries),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go newServer(c1, serverConfig)
_, _, _, err = NewClientConn(c2, "", clientConfig)
if tries > 2 {
if err == nil {
t.Fatalf("client: got no error, want %s", expectedErr)
} else if err.Error() != expectedErr.Error() {
t.Fatalf("client: got %s, want %s", err, expectedErr)
}
} else {
if err != nil {
t.Fatalf("client: got %s, want no error", err)
}
}
}
}
// Test if authentication attempts are correctly limited on server
// when more public keys are provided then MaxAuthTries
func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
signers := []Signer{}
for i := 0; i < 6; i++ {
signers = append(signers, testSigners["dsa"])
}
validConfig := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, validConfig); err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
Reason: 2,
Message: "too many authentication failures",
})
invalidConfig := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(append(signers, testSigners["rsa"])...),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, invalidConfig); err == nil {
t.Fatalf("client: got no error, want %s", expectedErr)
} else if err.Error() != expectedErr.Error() {
t.Fatalf("client: got %s, want %s", err, expectedErr)
}
}
// Test whether authentication errors are being properly logged if all
// authentication methods have been exhausted
func TestClientAuthErrorList(t *testing.T) {
publicKeyErr := errors.New("This is an error from PublicKeyCallback")
clientConfig := &ClientConfig{
Auth: []AuthMethod{
PublicKeys(testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
serverConfig := &ServerConfig{
PublicKeyCallback: func(_ ConnMetadata, _ PublicKey) (*Permissions, error) {
return nil, publicKeyErr
},
}
serverConfig.AddHostKey(testSigners["rsa"])
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewClientConn(c2, "", clientConfig)
_, err = newServer(c1, serverConfig)
if err == nil {
t.Fatal("newServer: got nil, expected errors")
}
authErrs, ok := err.(*ServerAuthError)
if !ok {
t.Fatalf("errors: got %T, want *ssh.ServerAuthError", err)
}
for i, e := range authErrs.Errors {
switch i {
case 0:
if e.Error() != "no auth passed yet" {
t.Fatalf("errors: got %v, want no auth passed yet", e.Error())
}
case 1:
if e != publicKeyErr {
t.Fatalf("errors: got %v, want %v", e, publicKeyErr)
}
default:
t.Fatalf("errors: got %v, expected 2 errors", authErrs.Errors)
}
}
}

View file

@ -1,81 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"net"
"strings"
"testing"
)
func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
receivedVersion := make(chan string, 1)
config.HostKeyCallback = InsecureIgnoreHostKey()
go func() {
version, err := readVersion(serverConn)
if err != nil {
receivedVersion <- ""
} else {
receivedVersion <- string(version)
}
serverConn.Close()
}()
NewClientConn(clientConn, "", config)
actual := <-receivedVersion
if actual != expected {
t.Fatalf("got %s; want %s", actual, expected)
}
}
func TestCustomClientVersion(t *testing.T) {
version := "Test-Client-Version-0.0"
testClientVersion(t, &ClientConfig{ClientVersion: version}, version)
}
func TestDefaultClientVersion(t *testing.T) {
testClientVersion(t, &ClientConfig{}, packageVersion)
}
func TestHostKeyCheck(t *testing.T) {
for _, tt := range []struct {
name string
wantError string
key PublicKey
}{
{"no callback", "must specify HostKeyCallback", nil},
{"correct key", "", testSigners["rsa"].PublicKey()},
{"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
} {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
serverConf := &ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["rsa"])
go NewServerConn(c1, serverConf)
clientConf := ClientConfig{
User: "user",
}
if tt.key != nil {
clientConf.HostKeyCallback = FixedHostKey(tt.key)
}
_, _, _, err = NewClientConn(c2, "", &clientConf)
if err != nil {
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
}
} else if tt.wantError != "" {
t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
}
}
}

View file

@ -24,11 +24,21 @@ const (
serviceSSH = "ssh-connection"
)
// supportedCiphers specifies the supported ciphers in preference order.
// supportedCiphers lists ciphers we support but might not recommend.
var supportedCiphers = []string{
"aes128-ctr", "aes192-ctr", "aes256-ctr",
"aes128-gcm@openssh.com",
"arcfour256", "arcfour128",
chacha20Poly1305ID,
"arcfour256", "arcfour128", "arcfour",
aes128cbcID,
tripledescbcID,
}
// preferredCiphers specifies the default preference for ciphers.
var preferredCiphers = []string{
"aes128-gcm@openssh.com",
chacha20Poly1305ID,
"aes128-ctr", "aes192-ctr", "aes256-ctr",
}
// supportedKexAlgos specifies the supported key-exchange algorithms in
@ -211,7 +221,7 @@ func (c *Config) SetDefaults() {
c.Rand = rand.Reader
}
if c.Ciphers == nil {
c.Ciphers = supportedCiphers
c.Ciphers = preferredCiphers
}
var ciphers []string
for _, c := range c.Ciphers {
@ -242,7 +252,7 @@ func (c *Config) SetDefaults() {
// buildDataSignedForAuth returns the data that is signed in order to prove
// possession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
data := struct {
Session []byte
Type byte
@ -253,7 +263,7 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK
Algo []byte
PubKey []byte
}{
sessionId,
sessionID,
msgUserAuthRequest,
req.User,
req.Service,

View file

@ -1,320 +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.
package ssh_test
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
func ExampleNewServerConn() {
// Public key authentication is done by comparing
// the public key of a received connection
// with the entries in the authorized_keys file.
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
if err != nil {
log.Fatalf("Failed to load authorized_keys, err: %v", err)
}
authorizedKeysMap := map[string]bool{}
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
log.Fatal(err)
}
authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ssh.ServerConfig{
// Remove to disable password auth.
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
// Should use constant-time compare (or better, salt+hash) in
// a production setting.
if c.User() == "testuser" && string(pass) == "tiger" {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
// Remove to disable public key auth.
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if authorizedKeysMap[string(pubKey.Marshal())] {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
log.Fatal("Failed to load private key: ", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("Failed to parse private key: ", err)
}
config.AddHostKey(private)
// Once a ServerConfig has been configured, connections can be
// accepted.
listener, err := net.Listen("tcp", "0.0.0.0:2022")
if err != nil {
log.Fatal("failed to listen for connection: ", err)
}
nConn, err := listener.Accept()
if err != nil {
log.Fatal("failed to accept incoming connection: ", err)
}
// Before use, a handshake must be performed on the incoming
// net.Conn.
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
if err != nil {
log.Fatal("failed to handshake: ", err)
}
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of a shell, the type is
// "session" and ServerShell may be used to present a simple
// terminal interface.
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Fatalf("Could not accept channel: %v", err)
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "shell" request.
go func(in <-chan *ssh.Request) {
for req := range in {
req.Reply(req.Type == "shell", nil)
}
}(requests)
term := terminal.NewTerminal(channel, "> ")
go func() {
defer channel.Close()
for {
line, err := term.ReadLine()
if err != nil {
break
}
fmt.Println(line)
}
}()
}
}
func ExampleHostKeyCheck() {
// Every client must provide a host key check. Here is a
// simple-minded parse of OpenSSH's known_hosts file
host := "hostname"
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
log.Fatalf("error parsing %q: %v", fields[2], err)
}
break
}
}
if hostKey == nil {
log.Fatalf("no hostkey for %s", host)
}
config := ssh.ClientConfig{
User: os.Getenv("USER"),
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
_, err = ssh.Dial("tcp", host+":22", &config)
log.Println(err)
}
func ExampleDial() {
var hostKey ssh.PublicKey
// An SSH client is represented with a ClientConn.
//
// To authenticate with the remote server you must pass at least one
// implementation of AuthMethod via the Auth field in ClientConfig,
// and provide a HostKeyCallback.
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("yourpassword"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
// Each ClientConn can support multiple interactive sessions,
// represented by a Session.
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
// Once a Session is created, you can execute a single command on
// the remote side using the Run method.
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
log.Fatal("Failed to run: " + err.Error())
}
fmt.Println(b.String())
}
func ExamplePublicKeys() {
var hostKey ssh.PublicKey
// A public key may be used to authenticate against the remote
// server by using an unencrypted PEM-encoded private key file.
//
// If you have an encrypted private key, the crypto/x509 package
// can be used to decrypt it.
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
}
func ExampleClient_Listen() {
var hostKey ssh.PublicKey
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Dial your ssh server.
conn, err := ssh.Dial("tcp", "localhost:22", config)
if err != nil {
log.Fatal("unable to connect: ", err)
}
defer conn.Close()
// Request the remote side to open port 8080 on all interfaces.
l, err := conn.Listen("tcp", "0.0.0.0:8080")
if err != nil {
log.Fatal("unable to register tcp forward: ", err)
}
defer l.Close()
// Serve HTTP with your SSH server acting as a reverse proxy.
http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "Hello world!\n")
}))
}
func ExampleSession_RequestPty() {
var hostKey ssh.PublicKey
// Create client config
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Connect to ssh server
conn, err := ssh.Dial("tcp", "localhost:22", config)
if err != nil {
log.Fatal("unable to connect: ", err)
}
defer conn.Close()
// Create a session
session, err := conn.NewSession()
if err != nil {
log.Fatal("unable to create session: ", err)
}
defer session.Close()
// Set up terminal modes
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty("xterm", 40, 80, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
// Start remote shell
if err := session.Shell(); err != nil {
log.Fatal("failed to start shell: ", err)
}
}

View file

@ -78,6 +78,11 @@ type handshakeTransport struct {
dialAddress string
remoteAddr net.Addr
// bannerCallback is non-empty if we are the client and it has been set in
// ClientConfig. In that case it is called during the user authentication
// dance to handle a custom server's message.
bannerCallback BannerCallback
// Algorithms agreed in the last key exchange.
algorithms *algorithms
@ -120,6 +125,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
t.dialAddress = dialAddr
t.remoteAddr = addr
t.hostKeyCallback = config.HostKeyCallback
t.bannerCallback = config.BannerCallback
if config.HostKeyAlgorithms != nil {
t.hostKeyAlgorithms = config.HostKeyAlgorithms
} else {

View file

@ -1,559 +0,0 @@
// Copyright 2013 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 ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
"reflect"
"runtime"
"strings"
"sync"
"testing"
)
type testChecker struct {
calls []string
}
func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
if dialAddr == "bad" {
return fmt.Errorf("dialAddr is bad")
}
if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil {
return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr)
}
t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal()))
return nil
}
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
// therefore is buffered (net.Pipe deadlocks if both sides start with
// a write.)
func netPipe() (net.Conn, net.Conn, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
listener, err = net.Listen("tcp", "[::1]:0")
if err != nil {
return nil, nil, err
}
}
defer listener.Close()
c1, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
return nil, nil, err
}
c2, err := listener.Accept()
if err != nil {
c1.Close()
return nil, nil, err
}
return c1, c2, nil
}
// noiseTransport inserts ignore messages to check that the read loop
// and the key exchange filters out these messages.
type noiseTransport struct {
keyingTransport
}
func (t *noiseTransport) writePacket(p []byte) error {
ignore := []byte{msgIgnore}
if err := t.keyingTransport.writePacket(ignore); err != nil {
return err
}
debug := []byte{msgDebug, 1, 2, 3}
if err := t.keyingTransport.writePacket(debug); err != nil {
return err
}
return t.keyingTransport.writePacket(p)
}
func addNoiseTransport(t keyingTransport) keyingTransport {
return &noiseTransport{t}
}
// handshakePair creates two handshakeTransports connected with each
// other. If the noise argument is true, both transports will try to
// confuse the other side by sending ignore and debug messages.
func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) {
a, b, err := netPipe()
if err != nil {
return nil, nil, err
}
var trC, trS keyingTransport
trC = newTransport(a, rand.Reader, true)
trS = newTransport(b, rand.Reader, false)
if noise {
trC = addNoiseTransport(trC)
trS = addNoiseTransport(trS)
}
clientConf.SetDefaults()
v := []byte("version")
client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr())
serverConf := &ServerConfig{}
serverConf.AddHostKey(testSigners["ecdsa"])
serverConf.AddHostKey(testSigners["rsa"])
serverConf.SetDefaults()
server = newServerTransport(trS, v, v, serverConf)
if err := server.waitSession(); err != nil {
return nil, nil, fmt.Errorf("server.waitSession: %v", err)
}
if err := client.waitSession(); err != nil {
return nil, nil, fmt.Errorf("client.waitSession: %v", err)
}
return client, server, nil
}
func TestHandshakeBasic(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("see golang.org/issue/7237")
}
checker := &syncChecker{
waitCall: make(chan int, 10),
called: make(chan int, 10),
}
checker.waitCall <- 1
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
// Let first kex complete normally.
<-checker.called
clientDone := make(chan int, 0)
gotHalf := make(chan int, 0)
const N = 20
go func() {
defer close(clientDone)
// Client writes a bunch of stuff, and does a key
// change in the middle. This should not confuse the
// handshake in progress. We do this twice, so we test
// that the packet buffer is reset correctly.
for i := 0; i < N; i++ {
p := []byte{msgRequestSuccess, byte(i)}
if err := trC.writePacket(p); err != nil {
t.Fatalf("sendPacket: %v", err)
}
if (i % 10) == 5 {
<-gotHalf
// halfway through, we request a key change.
trC.requestKeyExchange()
// Wait until we can be sure the key
// change has really started before we
// write more.
<-checker.called
}
if (i % 10) == 7 {
// write some packets until the kex
// completes, to test buffering of
// packets.
checker.waitCall <- 1
}
}
}()
// Server checks that client messages come in cleanly
i := 0
err = nil
for ; i < N; i++ {
var p []byte
p, err = trS.readPacket()
if err != nil {
break
}
if (i % 10) == 5 {
gotHalf <- 1
}
want := []byte{msgRequestSuccess, byte(i)}
if bytes.Compare(p, want) != 0 {
t.Errorf("message %d: got %v, want %v", i, p, want)
}
}
<-clientDone
if err != nil && err != io.EOF {
t.Fatalf("server error: %v", err)
}
if i != N {
t.Errorf("received %d messages, want 10.", i)
}
close(checker.called)
if _, ok := <-checker.called; ok {
// If all went well, we registered exactly 2 key changes: one
// that establishes the session, and one that we requested
// additionally.
t.Fatalf("got another host key checks after 2 handshakes")
}
}
func TestForceFirstKex(t *testing.T) {
// like handshakePair, but must access the keyingTransport.
checker := &testChecker{}
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
a, b, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
var trC, trS keyingTransport
trC = newTransport(a, rand.Reader, true)
// This is the disallowed packet:
trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
// Rest of the setup.
trS = newTransport(b, rand.Reader, false)
clientConf.SetDefaults()
v := []byte("version")
client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
serverConf := &ServerConfig{}
serverConf.AddHostKey(testSigners["ecdsa"])
serverConf.AddHostKey(testSigners["rsa"])
serverConf.SetDefaults()
server := newServerTransport(trS, v, v, serverConf)
defer client.Close()
defer server.Close()
// We setup the initial key exchange, but the remote side
// tries to send serviceRequestMsg in cleartext, which is
// disallowed.
if err := server.waitSession(); err == nil {
t.Errorf("server first kex init should reject unexpected packet")
}
}
func TestHandshakeAutoRekeyWrite(t *testing.T) {
checker := &syncChecker{
called: make(chan int, 10),
waitCall: nil,
}
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
clientConf.RekeyThreshold = 500
trC, trS, err := handshakePair(clientConf, "addr", false)
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
input := make([]byte, 251)
input[0] = msgRequestSuccess
done := make(chan int, 1)
const numPacket = 5
go func() {
defer close(done)
j := 0
for ; j < numPacket; j++ {
if p, err := trS.readPacket(); err != nil {
break
} else if !bytes.Equal(input, p) {
t.Errorf("got packet type %d, want %d", p[0], input[0])
}
}
if j != numPacket {
t.Errorf("got %d, want 5 messages", j)
}
}()
<-checker.called
for i := 0; i < numPacket; i++ {
p := make([]byte, len(input))
copy(p, input)
if err := trC.writePacket(p); err != nil {
t.Errorf("writePacket: %v", err)
}
if i == 2 {
// Make sure the kex is in progress.
<-checker.called
}
}
<-done
}
type syncChecker struct {
waitCall chan int
called chan int
}
func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
c.called <- 1
if c.waitCall != nil {
<-c.waitCall
}
return nil
}
func TestHandshakeAutoRekeyRead(t *testing.T) {
sync := &syncChecker{
called: make(chan int, 2),
waitCall: nil,
}
clientConf := &ClientConfig{
HostKeyCallback: sync.Check,
}
clientConf.RekeyThreshold = 500
trC, trS, err := handshakePair(clientConf, "addr", false)
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
packet := make([]byte, 501)
packet[0] = msgRequestSuccess
if err := trS.writePacket(packet); err != nil {
t.Fatalf("writePacket: %v", err)
}
// While we read out the packet, a key change will be
// initiated.
done := make(chan int, 1)
go func() {
defer close(done)
if _, err := trC.readPacket(); err != nil {
t.Fatalf("readPacket(client): %v", err)
}
}()
<-done
<-sync.called
}
// errorKeyingTransport generates errors after a given number of
// read/write operations.
type errorKeyingTransport struct {
packetConn
readLeft, writeLeft int
}
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
return nil
}
func (n *errorKeyingTransport) getSessionID() []byte {
return nil
}
func (n *errorKeyingTransport) writePacket(packet []byte) error {
if n.writeLeft == 0 {
n.Close()
return errors.New("barf")
}
n.writeLeft--
return n.packetConn.writePacket(packet)
}
func (n *errorKeyingTransport) readPacket() ([]byte, error) {
if n.readLeft == 0 {
n.Close()
return nil, errors.New("barf")
}
n.readLeft--
return n.packetConn.readPacket()
}
func TestHandshakeErrorHandlingRead(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, i, -1, false)
}
}
func TestHandshakeErrorHandlingWrite(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, -1, i, false)
}
}
func TestHandshakeErrorHandlingReadCoupled(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, i, -1, true)
}
}
func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
for i := 0; i < 20; i++ {
testHandshakeErrorHandlingN(t, -1, i, true)
}
}
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
// handshakeTransport deadlocks, the go runtime will detect it and
// panic.
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
a, b := memPipe()
defer a.Close()
defer b.Close()
key := testSigners["ecdsa"]
serverConf := Config{RekeyThreshold: minRekeyThreshold}
serverConf.SetDefaults()
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
serverConn.hostKeys = []Signer{key}
go serverConn.readLoop()
go serverConn.kexLoop()
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
clientConf.SetDefaults()
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
clientConn.hostKeyCallback = InsecureIgnoreHostKey()
go clientConn.readLoop()
go clientConn.kexLoop()
var wg sync.WaitGroup
for _, hs := range []packetConn{serverConn, clientConn} {
if !coupled {
wg.Add(2)
go func(c packetConn) {
for i := 0; ; i++ {
str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8)
err := c.writePacket(Marshal(&serviceRequestMsg{str}))
if err != nil {
break
}
}
wg.Done()
c.Close()
}(hs)
go func(c packetConn) {
for {
_, err := c.readPacket()
if err != nil {
break
}
}
wg.Done()
}(hs)
} else {
wg.Add(1)
go func(c packetConn) {
for {
_, err := c.readPacket()
if err != nil {
break
}
if err := c.writePacket(msg); err != nil {
break
}
}
wg.Done()
}(hs)
}
}
wg.Wait()
}
func TestDisconnect(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("see golang.org/issue/7237")
}
checker := &testChecker{}
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
errMsg := &disconnectMsg{
Reason: 42,
Message: "such is life",
}
trC.writePacket(Marshal(errMsg))
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
packet, err := trS.readPacket()
if err != nil {
t.Fatalf("readPacket 1: %v", err)
}
if packet[0] != msgRequestSuccess {
t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess)
}
_, err = trS.readPacket()
if err == nil {
t.Errorf("readPacket 2 succeeded")
} else if !reflect.DeepEqual(err, errMsg) {
t.Errorf("got error %#v, want %#v", err, errMsg)
}
_, err = trS.readPacket()
if err == nil {
t.Errorf("readPacket 3 succeeded")
}
}
func TestHandshakeRekeyDefault(t *testing.T) {
clientConf := &ClientConfig{
Config: Config{
Ciphers: []string{"aes128-ctr"},
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
trC, trS, err := handshakePair(clientConf, "addr", false)
if err != nil {
t.Fatalf("handshakePair: %v", err)
}
defer trC.Close()
defer trS.Close()
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
trC.Close()
rgb := (1024 + trC.readBytesLeft) >> 30
wgb := (1024 + trC.writeBytesLeft) >> 30
if rgb != 64 {
t.Errorf("got rekey after %dG read, want 64G", rgb)
}
if wgb != 64 {
t.Errorf("got rekey after %dG write, want 64G", wgb)
}
}

View file

@ -119,7 +119,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
return nil, err
}
kInt, err := group.diffieHellman(kexDHReply.Y, x)
ki, err := group.diffieHellman(kexDHReply.Y, x)
if err != nil {
return nil, err
}
@ -129,8 +129,8 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
writeString(h, kexDHReply.HostKey)
writeInt(h, X)
writeInt(h, kexDHReply.Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
K := make([]byte, intLength(ki))
marshalInt(K, ki)
h.Write(K)
return &kexResult{
@ -164,7 +164,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
}
Y := new(big.Int).Exp(group.g, y, group.p)
kInt, err := group.diffieHellman(kexDHInit.X, y)
ki, err := group.diffieHellman(kexDHInit.X, y)
if err != nil {
return nil, err
}
@ -177,8 +177,8 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
writeInt(h, kexDHInit.X)
writeInt(h, Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
K := make([]byte, intLength(ki))
marshalInt(K, ki)
h.Write(K)
H := h.Sum(nil)
@ -462,9 +462,9 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
writeString(h, kp.pub[:])
writeString(h, reply.EphemeralPubKey)
kInt := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
ki := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(ki))
marshalInt(K, ki)
h.Write(K)
return &kexResult{
@ -510,9 +510,9 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
writeString(h, kexInit.ClientPubKey)
writeString(h, kp.pub[:])
kInt := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
ki := new(big.Int).SetBytes(secret[:])
K := make([]byte, intLength(ki))
marshalInt(K, ki)
h.Write(K)
H := h.Sum(nil)

View file

@ -1,50 +0,0 @@
// Copyright 2013 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 ssh
// Key exchange tests.
import (
"crypto/rand"
"reflect"
"testing"
)
func TestKexes(t *testing.T) {
type kexResultErr struct {
result *kexResult
err error
}
for name, kex := range kexAlgoMap {
a, b := memPipe()
s := make(chan kexResultErr, 1)
c := make(chan kexResultErr, 1)
var magics handshakeMagics
go func() {
r, e := kex.Client(a, rand.Reader, &magics)
a.Close()
c <- kexResultErr{r, e}
}()
go func() {
r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"])
b.Close()
s <- kexResultErr{r, e}
}()
clientRes := <-c
serverRes := <-s
if clientRes.err != nil {
t.Errorf("client: %v", clientRes.err)
}
if serverRes.err != nil {
t.Errorf("server: %v", serverRes.err)
}
if !reflect.DeepEqual(clientRes.result, serverRes.result) {
t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result)
}
}
}

View file

@ -276,7 +276,8 @@ type PublicKey interface {
Type() string
// Marshal returns the serialized key data in SSH wire format,
// with the name prefix.
// with the name prefix. To unmarshal the returned data, use
// the ParsePublicKey function.
Marshal() []byte
// Verify that sig is a signature on the given data using this
@ -363,7 +364,7 @@ func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
type dsaPublicKey dsa.PublicKey
func (r *dsaPublicKey) Type() string {
func (k *dsaPublicKey) Type() string {
return "ssh-dss"
}
@ -481,12 +482,12 @@ func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
type ecdsaPublicKey ecdsa.PublicKey
func (key *ecdsaPublicKey) Type() string {
return "ecdsa-sha2-" + key.nistID()
func (k *ecdsaPublicKey) Type() string {
return "ecdsa-sha2-" + k.nistID()
}
func (key *ecdsaPublicKey) nistID() string {
switch key.Params().BitSize {
func (k *ecdsaPublicKey) nistID() string {
switch k.Params().BitSize {
case 256:
return "nistp256"
case 384:
@ -499,7 +500,7 @@ func (key *ecdsaPublicKey) nistID() string {
type ed25519PublicKey ed25519.PublicKey
func (key ed25519PublicKey) Type() string {
func (k ed25519PublicKey) Type() string {
return KeyAlgoED25519
}
@ -518,23 +519,23 @@ func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
return (ed25519PublicKey)(key), w.Rest, nil
}
func (key ed25519PublicKey) Marshal() []byte {
func (k ed25519PublicKey) Marshal() []byte {
w := struct {
Name string
KeyBytes []byte
}{
KeyAlgoED25519,
[]byte(key),
[]byte(k),
}
return Marshal(&w)
}
func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
if sig.Format != key.Type() {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error {
if sig.Format != k.Type() {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
}
edKey := (ed25519.PublicKey)(key)
edKey := (ed25519.PublicKey)(k)
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
return errors.New("ssh: signature did not verify")
}
@ -595,9 +596,9 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
return (*ecdsaPublicKey)(key), w.Rest, nil
}
func (key *ecdsaPublicKey) Marshal() []byte {
func (k *ecdsaPublicKey) Marshal() []byte {
// See RFC 5656, section 3.1.
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y)
// ECDSA publickey struct layout should match the struct used by
// parseECDSACert in the x/crypto/ssh/agent package.
w := struct {
@ -605,20 +606,20 @@ func (key *ecdsaPublicKey) Marshal() []byte {
ID string
Key []byte
}{
key.Type(),
key.nistID(),
k.Type(),
k.nistID(),
keyBytes,
}
return Marshal(&w)
}
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
if sig.Format != key.Type() {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
if sig.Format != k.Type() {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
}
h := ecHash(key.Curve).New()
h := ecHash(k.Curve).New()
h.Write(data)
digest := h.Sum(nil)
@ -635,7 +636,7 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
return err
}
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) {
return nil
}
return errors.New("ssh: signature did not verify")
@ -758,7 +759,7 @@ func NewPublicKey(key interface{}) (PublicKey, error) {
return (*rsaPublicKey)(key), nil
case *ecdsa.PublicKey:
if !supportedEllipticCurve(key.Curve) {
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported")
}
return (*ecdsaPublicKey)(key), nil
case *dsa.PublicKey:

View file

@ -1,500 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"fmt"
"reflect"
"strings"
"testing"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh/testdata"
)
func rawKey(pub PublicKey) interface{} {
switch k := pub.(type) {
case *rsaPublicKey:
return (*rsa.PublicKey)(k)
case *dsaPublicKey:
return (*dsa.PublicKey)(k)
case *ecdsaPublicKey:
return (*ecdsa.PublicKey)(k)
case ed25519PublicKey:
return (ed25519.PublicKey)(k)
case *Certificate:
return k
}
panic("unknown key type")
}
func TestKeyMarshalParse(t *testing.T) {
for _, priv := range testSigners {
pub := priv.PublicKey()
roundtrip, err := ParsePublicKey(pub.Marshal())
if err != nil {
t.Errorf("ParsePublicKey(%T): %v", pub, err)
}
k1 := rawKey(pub)
k2 := rawKey(roundtrip)
if !reflect.DeepEqual(k1, k2) {
t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
}
}
}
func TestUnsupportedCurves(t *testing.T) {
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
}
if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
}
}
func TestNewPublicKey(t *testing.T) {
for _, k := range testSigners {
raw := rawKey(k.PublicKey())
// Skip certificates, as NewPublicKey does not support them.
if _, ok := raw.(*Certificate); ok {
continue
}
pub, err := NewPublicKey(raw)
if err != nil {
t.Errorf("NewPublicKey(%#v): %v", raw, err)
}
if !reflect.DeepEqual(k.PublicKey(), pub) {
t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
}
}
}
func TestKeySignVerify(t *testing.T) {
for _, priv := range testSigners {
pub := priv.PublicKey()
data := []byte("sign me")
sig, err := priv.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("Sign(%T): %v", priv, err)
}
if err := pub.Verify(data, sig); err != nil {
t.Errorf("publicKey.Verify(%T): %v", priv, err)
}
sig.Blob[5]++
if err := pub.Verify(data, sig); err == nil {
t.Errorf("publicKey.Verify on broken sig did not fail")
}
}
}
func TestParseRSAPrivateKey(t *testing.T) {
key := testPrivateKeys["rsa"]
rsa, ok := key.(*rsa.PrivateKey)
if !ok {
t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
}
if err := rsa.Validate(); err != nil {
t.Errorf("Validate: %v", err)
}
}
func TestParseECPrivateKey(t *testing.T) {
key := testPrivateKeys["ecdsa"]
ecKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
}
if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
t.Fatalf("public key does not validate.")
}
}
// See Issue https://github.com/golang/go/issues/6650.
func TestParseEncryptedPrivateKeysFails(t *testing.T) {
const wantSubstring = "encrypted"
for i, tt := range testdata.PEMEncryptedKeys {
_, err := ParsePrivateKey(tt.PEMBytes)
if err == nil {
t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name)
continue
}
if !strings.Contains(err.Error(), wantSubstring) {
t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring)
}
}
}
// Parse encrypted private keys with passphrase
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
data := []byte("sign me")
for _, tt := range testdata.PEMEncryptedKeys {
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
if err != nil {
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
continue
}
sig, err := s.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("dsa.Sign: %v", err)
}
if err := s.PublicKey().Verify(data, sig); err != nil {
t.Errorf("Verify failed: %v", err)
}
}
tt := testdata.PEMEncryptedKeys[0]
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
if err != x509.IncorrectPasswordError {
t.Fatalf("got %v want IncorrectPasswordError", err)
}
}
func TestParseDSA(t *testing.T) {
// We actually exercise the ParsePrivateKey codepath here, as opposed to
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
// uses.
s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
if err != nil {
t.Fatalf("ParsePrivateKey returned error: %s", err)
}
data := []byte("sign me")
sig, err := s.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("dsa.Sign: %v", err)
}
if err := s.PublicKey().Verify(data, sig); err != nil {
t.Errorf("Verify failed: %v", err)
}
}
// Tests for authorized_keys parsing.
// getTestKey returns a public key, and its base64 encoding.
func getTestKey() (PublicKey, string) {
k := testPublicKeys["rsa"]
b := &bytes.Buffer{}
e := base64.NewEncoder(base64.StdEncoding, b)
e.Write(k.Marshal())
e.Close()
return k, b.String()
}
func TestMarshalParsePublicKey(t *testing.T) {
pub, pubSerialized := getTestKey()
line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
authKeys := MarshalAuthorizedKey(pub)
actualFields := strings.Fields(string(authKeys))
if len(actualFields) == 0 {
t.Fatalf("failed authKeys: %v", authKeys)
}
// drop the comment
expectedFields := strings.Fields(line)[0:2]
if !reflect.DeepEqual(actualFields, expectedFields) {
t.Errorf("got %v, expected %v", actualFields, expectedFields)
}
actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
if err != nil {
t.Fatalf("cannot parse %v: %v", line, err)
}
if !reflect.DeepEqual(actPub, pub) {
t.Errorf("got %v, expected %v", actPub, pub)
}
}
type authResult struct {
pubKey PublicKey
options []string
comments string
rest string
ok bool
}
func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
rest := authKeys
var values []authResult
for len(rest) > 0 {
var r authResult
var err error
r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
r.ok = (err == nil)
t.Log(err)
r.rest = string(rest)
values = append(values, r)
}
if !reflect.DeepEqual(values, expected) {
t.Errorf("got %#v, expected %#v", values, expected)
}
}
func TestAuthorizedKeyBasic(t *testing.T) {
pub, pubSerialized := getTestKey()
line := "ssh-rsa " + pubSerialized + " user@host"
testAuthorizedKeys(t, []byte(line),
[]authResult{
{pub, nil, "user@host", "", true},
})
}
func TestAuth(t *testing.T) {
pub, pubSerialized := getTestKey()
authWithOptions := []string{
`# comments to ignore before any keys...`,
``,
`env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
`# comments to ignore, along with a blank line`,
``,
`env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
``,
`# more comments, plus a invalid entry`,
`ssh-rsa data-that-will-not-parse user@host3`,
}
for _, eol := range []string{"\n", "\r\n"} {
authOptions := strings.Join(authWithOptions, eol)
rest2 := strings.Join(authWithOptions[3:], eol)
rest3 := strings.Join(authWithOptions[6:], eol)
testAuthorizedKeys(t, []byte(authOptions), []authResult{
{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
{nil, nil, "", "", false},
})
}
}
func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
pub, pubSerialized := getTestKey()
authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
{pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
})
}
func TestAuthWithQuotedCommaInEnv(t *testing.T) {
pub, pubSerialized := getTestKey()
authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
{pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
})
}
func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
pub, pubSerialized := getTestKey()
authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
{pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
})
testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
{pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
})
}
func TestAuthWithInvalidSpace(t *testing.T) {
_, pubSerialized := getTestKey()
authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
#more to follow but still no valid keys`)
testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
{nil, nil, "", "", false},
})
}
func TestAuthWithMissingQuote(t *testing.T) {
pub, pubSerialized := getTestKey()
authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
{pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
})
}
func TestInvalidEntry(t *testing.T) {
authInvalid := []byte(`ssh-rsa`)
_, _, _, _, err := ParseAuthorizedKey(authInvalid)
if err == nil {
t.Errorf("got valid entry for %q", authInvalid)
}
}
var knownHostsParseTests = []struct {
input string
err string
marker string
comment string
hosts []string
rest string
}{
{
"",
"EOF",
"", "", nil, "",
},
{
"# Just a comment",
"EOF",
"", "", nil, "",
},
{
" \t ",
"EOF",
"", "", nil, "",
},
{
"localhost ssh-rsa {RSAPUB}",
"",
"", "", []string{"localhost"}, "",
},
{
"localhost\tssh-rsa {RSAPUB}",
"",
"", "", []string{"localhost"}, "",
},
{
"localhost\tssh-rsa {RSAPUB}\tcomment comment",
"",
"", "comment comment", []string{"localhost"}, "",
},
{
"localhost\tssh-rsa {RSAPUB}\tcomment comment\n",
"",
"", "comment comment", []string{"localhost"}, "",
},
{
"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n",
"",
"", "comment comment", []string{"localhost"}, "",
},
{
"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line",
"",
"", "comment comment", []string{"localhost"}, "next line",
},
{
"localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
"",
"", "comment comment", []string{"localhost", "[host2:123]"}, "",
},
{
"@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
"",
"marker", "", []string{"localhost", "[host2:123]"}, "",
},
{
"@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
"short read",
"", "", nil, "",
},
}
func TestKnownHostsParsing(t *testing.T) {
rsaPub, rsaPubSerialized := getTestKey()
for i, test := range knownHostsParseTests {
var expectedKey PublicKey
const rsaKeyToken = "{RSAPUB}"
input := test.input
if strings.Contains(input, rsaKeyToken) {
expectedKey = rsaPub
input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
}
marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
if err != nil {
if len(test.err) == 0 {
t.Errorf("#%d: unexpectedly failed with %q", i, err)
} else if !strings.Contains(err.Error(), test.err) {
t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err)
}
continue
} else if len(test.err) != 0 {
t.Errorf("#%d: succeeded but expected error including %q", i, test.err)
continue
}
if !reflect.DeepEqual(expectedKey, pubKey) {
t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey)
}
if marker != test.marker {
t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker)
}
if comment != test.comment {
t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment)
}
if !reflect.DeepEqual(test.hosts, hosts) {
t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts)
}
if rest := string(rest); rest != test.rest {
t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest)
}
}
}
func TestFingerprintLegacyMD5(t *testing.T) {
pub, _ := getTestKey()
fingerprint := FingerprintLegacyMD5(pub)
want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa
if fingerprint != want {
t.Errorf("got fingerprint %q want %q", fingerprint, want)
}
}
func TestFingerprintSHA256(t *testing.T) {
pub, _ := getTestKey()
fingerprint := FingerprintSHA256(pub)
want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa
if fingerprint != want {
t.Errorf("got fingerprint %q want %q", fingerprint, want)
}
}

View file

@ -1,110 +0,0 @@
// Copyright 2013 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 ssh
import (
"io"
"sync"
"testing"
)
// An in-memory packetConn. It is safe to call Close and writePacket
// from different goroutines.
type memTransport struct {
eof bool
pending [][]byte
write *memTransport
sync.Mutex
*sync.Cond
}
func (t *memTransport) readPacket() ([]byte, error) {
t.Lock()
defer t.Unlock()
for {
if len(t.pending) > 0 {
r := t.pending[0]
t.pending = t.pending[1:]
return r, nil
}
if t.eof {
return nil, io.EOF
}
t.Cond.Wait()
}
}
func (t *memTransport) closeSelf() error {
t.Lock()
defer t.Unlock()
if t.eof {
return io.EOF
}
t.eof = true
t.Cond.Broadcast()
return nil
}
func (t *memTransport) Close() error {
err := t.write.closeSelf()
t.closeSelf()
return err
}
func (t *memTransport) writePacket(p []byte) error {
t.write.Lock()
defer t.write.Unlock()
if t.write.eof {
return io.EOF
}
c := make([]byte, len(p))
copy(c, p)
t.write.pending = append(t.write.pending, c)
t.write.Cond.Signal()
return nil
}
func memPipe() (a, b packetConn) {
t1 := memTransport{}
t2 := memTransport{}
t1.write = &t2
t2.write = &t1
t1.Cond = sync.NewCond(&t1.Mutex)
t2.Cond = sync.NewCond(&t2.Mutex)
return &t1, &t2
}
func TestMemPipe(t *testing.T) {
a, b := memPipe()
if err := a.writePacket([]byte{42}); err != nil {
t.Fatalf("writePacket: %v", err)
}
if err := a.Close(); err != nil {
t.Fatal("Close: ", err)
}
p, err := b.readPacket()
if err != nil {
t.Fatal("readPacket: ", err)
}
if len(p) != 1 || p[0] != 42 {
t.Fatalf("got %v, want {42}", p)
}
p, err = b.readPacket()
if err != io.EOF {
t.Fatalf("got %v, %v, want EOF", p, err)
}
}
func TestDoubleClose(t *testing.T) {
a, _ := memPipe()
err := a.Close()
if err != nil {
t.Errorf("Close: %v", err)
}
err = a.Close()
if err != io.EOF {
t.Errorf("expect EOF on double close.")
}
}

View file

@ -23,10 +23,6 @@ const (
msgUnimplemented = 3
msgDebug = 4
msgNewKeys = 21
// Standard authentication messages
msgUserAuthSuccess = 52
msgUserAuthBanner = 53
)
// SSH messages:
@ -137,6 +133,18 @@ type userAuthFailureMsg struct {
PartialSuccess bool
}
// See RFC 4252, section 5.1
const msgUserAuthSuccess = 52
// See RFC 4252, section 5.4
const msgUserAuthBanner = 53
type userAuthBannerMsg struct {
Message string `sshtype:"53"`
// unused, but required to allow message parsing
Language string
}
// See RFC 4256, section 3.2
const msgUserAuthInfoRequest = 60
const msgUserAuthInfoResponse = 61
@ -154,7 +162,7 @@ const msgChannelOpen = 90
type channelOpenMsg struct {
ChanType string `sshtype:"90"`
PeersId uint32
PeersID uint32
PeersWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
@ -165,7 +173,7 @@ const msgChannelData = 94
// Used for debug print outs of packets.
type channelDataMsg struct {
PeersId uint32 `sshtype:"94"`
PeersID uint32 `sshtype:"94"`
Length uint32
Rest []byte `ssh:"rest"`
}
@ -174,8 +182,8 @@ type channelDataMsg struct {
const msgChannelOpenConfirm = 91
type channelOpenConfirmMsg struct {
PeersId uint32 `sshtype:"91"`
MyId uint32
PeersID uint32 `sshtype:"91"`
MyID uint32
MyWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
@ -185,7 +193,7 @@ type channelOpenConfirmMsg struct {
const msgChannelOpenFailure = 92
type channelOpenFailureMsg struct {
PeersId uint32 `sshtype:"92"`
PeersID uint32 `sshtype:"92"`
Reason RejectionReason
Message string
Language string
@ -194,7 +202,7 @@ type channelOpenFailureMsg struct {
const msgChannelRequest = 98
type channelRequestMsg struct {
PeersId uint32 `sshtype:"98"`
PeersID uint32 `sshtype:"98"`
Request string
WantReply bool
RequestSpecificData []byte `ssh:"rest"`
@ -204,28 +212,28 @@ type channelRequestMsg struct {
const msgChannelSuccess = 99
type channelRequestSuccessMsg struct {
PeersId uint32 `sshtype:"99"`
PeersID uint32 `sshtype:"99"`
}
// See RFC 4254, section 5.4.
const msgChannelFailure = 100
type channelRequestFailureMsg struct {
PeersId uint32 `sshtype:"100"`
PeersID uint32 `sshtype:"100"`
}
// See RFC 4254, section 5.3
const msgChannelClose = 97
type channelCloseMsg struct {
PeersId uint32 `sshtype:"97"`
PeersID uint32 `sshtype:"97"`
}
// See RFC 4254, section 5.3
const msgChannelEOF = 96
type channelEOFMsg struct {
PeersId uint32 `sshtype:"96"`
PeersID uint32 `sshtype:"96"`
}
// See RFC 4254, section 4
@ -255,7 +263,7 @@ type globalRequestFailureMsg struct {
const msgChannelWindowAdjust = 93
type windowAdjustMsg struct {
PeersId uint32 `sshtype:"93"`
PeersID uint32 `sshtype:"93"`
AdditionalBytes uint32
}

View file

@ -1,288 +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.
package ssh
import (
"bytes"
"math/big"
"math/rand"
"reflect"
"testing"
"testing/quick"
)
var intLengthTests = []struct {
val, length int
}{
{0, 4 + 0},
{1, 4 + 1},
{127, 4 + 1},
{128, 4 + 2},
{-1, 4 + 1},
}
func TestIntLength(t *testing.T) {
for _, test := range intLengthTests {
v := new(big.Int).SetInt64(int64(test.val))
length := intLength(v)
if length != test.length {
t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
}
}
}
type msgAllTypes struct {
Bool bool `sshtype:"21"`
Array [16]byte
Uint64 uint64
Uint32 uint32
Uint8 uint8
String string
Strings []string
Bytes []byte
Int *big.Int
Rest []byte `ssh:"rest"`
}
func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value {
m := &msgAllTypes{}
m.Bool = rand.Intn(2) == 1
randomBytes(m.Array[:], rand)
m.Uint64 = uint64(rand.Int63n(1<<63 - 1))
m.Uint32 = uint32(rand.Intn((1 << 31) - 1))
m.Uint8 = uint8(rand.Intn(1 << 8))
m.String = string(m.Array[:])
m.Strings = randomNameList(rand)
m.Bytes = m.Array[:]
m.Int = randomInt(rand)
m.Rest = m.Array[:]
return reflect.ValueOf(m)
}
func TestMarshalUnmarshal(t *testing.T) {
rand := rand.New(rand.NewSource(0))
iface := &msgAllTypes{}
ty := reflect.ValueOf(iface).Type()
n := 100
if testing.Short() {
n = 5
}
for j := 0; j < n; j++ {
v, ok := quick.Value(ty, rand)
if !ok {
t.Errorf("failed to create value")
break
}
m1 := v.Elem().Interface()
m2 := iface
marshaled := Marshal(m1)
if err := Unmarshal(marshaled, m2); err != nil {
t.Errorf("Unmarshal %#v: %s", m1, err)
break
}
if !reflect.DeepEqual(v.Interface(), m2) {
t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled)
break
}
}
}
func TestUnmarshalEmptyPacket(t *testing.T) {
var b []byte
var m channelRequestSuccessMsg
if err := Unmarshal(b, &m); err == nil {
t.Fatalf("unmarshal of empty slice succeeded")
}
}
func TestUnmarshalUnexpectedPacket(t *testing.T) {
type S struct {
I uint32 `sshtype:"43"`
S string
B bool
}
s := S{11, "hello", true}
packet := Marshal(s)
packet[0] = 42
roundtrip := S{}
err := Unmarshal(packet, &roundtrip)
if err == nil {
t.Fatal("expected error, not nil")
}
}
func TestMarshalPtr(t *testing.T) {
s := struct {
S string
}{"hello"}
m1 := Marshal(s)
m2 := Marshal(&s)
if !bytes.Equal(m1, m2) {
t.Errorf("got %q, want %q for marshaled pointer", m2, m1)
}
}
func TestBareMarshalUnmarshal(t *testing.T) {
type S struct {
I uint32
S string
B bool
}
s := S{42, "hello", true}
packet := Marshal(s)
roundtrip := S{}
Unmarshal(packet, &roundtrip)
if !reflect.DeepEqual(s, roundtrip) {
t.Errorf("got %#v, want %#v", roundtrip, s)
}
}
func TestBareMarshal(t *testing.T) {
type S2 struct {
I uint32
}
s := S2{42}
packet := Marshal(s)
i, rest, ok := parseUint32(packet)
if len(rest) > 0 || !ok {
t.Errorf("parseInt(%q): parse error", packet)
}
if i != s.I {
t.Errorf("got %d, want %d", i, s.I)
}
}
func TestUnmarshalShortKexInitPacket(t *testing.T) {
// This used to panic.
// Issue 11348
packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff}
kim := &kexInitMsg{}
if err := Unmarshal(packet, kim); err == nil {
t.Error("truncated packet unmarshaled without error")
}
}
func TestMarshalMultiTag(t *testing.T) {
var res struct {
A uint32 `sshtype:"1|2"`
}
good1 := struct {
A uint32 `sshtype:"1"`
}{
1,
}
good2 := struct {
A uint32 `sshtype:"2"`
}{
1,
}
if e := Unmarshal(Marshal(good1), &res); e != nil {
t.Errorf("error unmarshaling multipart tag: %v", e)
}
if e := Unmarshal(Marshal(good2), &res); e != nil {
t.Errorf("error unmarshaling multipart tag: %v", e)
}
bad1 := struct {
A uint32 `sshtype:"3"`
}{
1,
}
if e := Unmarshal(Marshal(bad1), &res); e == nil {
t.Errorf("bad struct unmarshaled without error")
}
}
func randomBytes(out []byte, rand *rand.Rand) {
for i := 0; i < len(out); i++ {
out[i] = byte(rand.Int31())
}
}
func randomNameList(rand *rand.Rand) []string {
ret := make([]string, rand.Int31()&15)
for i := range ret {
s := make([]byte, 1+(rand.Int31()&15))
for j := range s {
s[j] = 'a' + uint8(rand.Int31()&15)
}
ret[i] = string(s)
}
return ret
}
func randomInt(rand *rand.Rand) *big.Int {
return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
}
func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
ki := &kexInitMsg{}
randomBytes(ki.Cookie[:], rand)
ki.KexAlgos = randomNameList(rand)
ki.ServerHostKeyAlgos = randomNameList(rand)
ki.CiphersClientServer = randomNameList(rand)
ki.CiphersServerClient = randomNameList(rand)
ki.MACsClientServer = randomNameList(rand)
ki.MACsServerClient = randomNameList(rand)
ki.CompressionClientServer = randomNameList(rand)
ki.CompressionServerClient = randomNameList(rand)
ki.LanguagesClientServer = randomNameList(rand)
ki.LanguagesServerClient = randomNameList(rand)
if rand.Int31()&1 == 1 {
ki.FirstKexFollows = true
}
return reflect.ValueOf(ki)
}
func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
dhi := &kexDHInitMsg{}
dhi.X = randomInt(rand)
return reflect.ValueOf(dhi)
}
var (
_kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
_kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
_kexInit = Marshal(_kexInitMsg)
_kexDHInit = Marshal(_kexDHInitMsg)
)
func BenchmarkMarshalKexInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
Marshal(_kexInitMsg)
}
}
func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
m := new(kexInitMsg)
for i := 0; i < b.N; i++ {
Unmarshal(_kexInit, m)
}
}
func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
Marshal(_kexDHInitMsg)
}
}
func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) {
m := new(kexDHInitMsg)
for i := 0; i < b.N; i++ {
Unmarshal(_kexDHInit, m)
}
}

View file

@ -278,7 +278,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
failMsg := channelOpenFailureMsg{
PeersId: msg.PeersId,
PeersID: msg.PeersID,
Reason: ConnectionFailed,
Message: "invalid request",
Language: "en_US.UTF-8",
@ -287,7 +287,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
}
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
c.remoteId = msg.PeersId
c.remoteId = msg.PeersID
c.maxRemotePayload = msg.MaxPacketSize
c.remoteWin.add(msg.PeersWindow)
m.incomingChannels <- c
@ -313,7 +313,7 @@ func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
PeersWindow: ch.myWindow,
MaxPacketSize: ch.maxIncomingPayload,
TypeSpecificData: extra,
PeersId: ch.localId,
PeersID: ch.localId,
}
if err := m.sendMessage(open); err != nil {
return nil, err

View file

@ -1,505 +0,0 @@
// Copyright 2013 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 ssh
import (
"io"
"io/ioutil"
"sync"
"testing"
)
func muxPair() (*mux, *mux) {
a, b := memPipe()
s := newMux(a)
c := newMux(b)
return s, c
}
// Returns both ends of a channel, and the mux for the the 2nd
// channel.
func channelPair(t *testing.T) (*channel, *channel, *mux) {
c, s := muxPair()
res := make(chan *channel, 1)
go func() {
newCh, ok := <-s.incomingChannels
if !ok {
t.Fatalf("No incoming channel")
}
if newCh.ChannelType() != "chan" {
t.Fatalf("got type %q want chan", newCh.ChannelType())
}
ch, _, err := newCh.Accept()
if err != nil {
t.Fatalf("Accept %v", err)
}
res <- ch.(*channel)
}()
ch, err := c.openChannel("chan", nil)
if err != nil {
t.Fatalf("OpenChannel: %v", err)
}
return <-res, ch, c
}
// Test that stderr and stdout can be addressed from different
// goroutines. This is intended for use with the race detector.
func TestMuxChannelExtendedThreadSafety(t *testing.T) {
writer, reader, mux := channelPair(t)
defer writer.Close()
defer reader.Close()
defer mux.Close()
var wr, rd sync.WaitGroup
magic := "hello world"
wr.Add(2)
go func() {
io.WriteString(writer, magic)
wr.Done()
}()
go func() {
io.WriteString(writer.Stderr(), magic)
wr.Done()
}()
rd.Add(2)
go func() {
c, err := ioutil.ReadAll(reader)
if string(c) != magic {
t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err)
}
rd.Done()
}()
go func() {
c, err := ioutil.ReadAll(reader.Stderr())
if string(c) != magic {
t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err)
}
rd.Done()
}()
wr.Wait()
writer.CloseWrite()
rd.Wait()
}
func TestMuxReadWrite(t *testing.T) {
s, c, mux := channelPair(t)
defer s.Close()
defer c.Close()
defer mux.Close()
magic := "hello world"
magicExt := "hello stderr"
go func() {
_, err := s.Write([]byte(magic))
if err != nil {
t.Fatalf("Write: %v", err)
}
_, err = s.Extended(1).Write([]byte(magicExt))
if err != nil {
t.Fatalf("Write: %v", err)
}
err = s.Close()
if err != nil {
t.Fatalf("Close: %v", err)
}
}()
var buf [1024]byte
n, err := c.Read(buf[:])
if err != nil {
t.Fatalf("server Read: %v", err)
}
got := string(buf[:n])
if got != magic {
t.Fatalf("server: got %q want %q", got, magic)
}
n, err = c.Extended(1).Read(buf[:])
if err != nil {
t.Fatalf("server Read: %v", err)
}
got = string(buf[:n])
if got != magicExt {
t.Fatalf("server: got %q want %q", got, magic)
}
}
func TestMuxChannelOverflow(t *testing.T) {
reader, writer, mux := channelPair(t)
defer reader.Close()
defer writer.Close()
defer mux.Close()
wDone := make(chan int, 1)
go func() {
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err)
}
writer.Write(make([]byte, 1))
wDone <- 1
}()
writer.remoteWin.waitWriterBlocked()
// Send 1 byte.
packet := make([]byte, 1+4+4+1)
packet[0] = msgChannelData
marshalUint32(packet[1:], writer.remoteId)
marshalUint32(packet[5:], uint32(1))
packet[9] = 42
if err := writer.mux.conn.writePacket(packet); err != nil {
t.Errorf("could not send packet")
}
if _, err := reader.SendRequest("hello", true, nil); err == nil {
t.Errorf("SendRequest succeeded.")
}
<-wDone
}
func TestMuxChannelCloseWriteUnblock(t *testing.T) {
reader, writer, mux := channelPair(t)
defer reader.Close()
defer writer.Close()
defer mux.Close()
wDone := make(chan int, 1)
go func() {
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err)
}
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
t.Errorf("got %v, want EOF for unblock write", err)
}
wDone <- 1
}()
writer.remoteWin.waitWriterBlocked()
reader.Close()
<-wDone
}
func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
reader, writer, mux := channelPair(t)
defer reader.Close()
defer writer.Close()
defer mux.Close()
wDone := make(chan int, 1)
go func() {
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err)
}
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
t.Errorf("got %v, want EOF for unblock write", err)
}
wDone <- 1
}()
writer.remoteWin.waitWriterBlocked()
mux.Close()
<-wDone
}
func TestMuxReject(t *testing.T) {
client, server := muxPair()
defer server.Close()
defer client.Close()
go func() {
ch, ok := <-server.incomingChannels
if !ok {
t.Fatalf("Accept")
}
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
}
ch.Reject(RejectionReason(42), "message")
}()
ch, err := client.openChannel("ch", []byte("extra"))
if ch != nil {
t.Fatal("openChannel not rejected")
}
ocf, ok := err.(*OpenChannelError)
if !ok {
t.Errorf("got %#v want *OpenChannelError", err)
} else if ocf.Reason != 42 || ocf.Message != "message" {
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
}
want := "ssh: rejected: unknown reason 42 (message)"
if err.Error() != want {
t.Errorf("got %q, want %q", err.Error(), want)
}
}
func TestMuxChannelRequest(t *testing.T) {
client, server, mux := channelPair(t)
defer server.Close()
defer client.Close()
defer mux.Close()
var received int
var wg sync.WaitGroup
wg.Add(1)
go func() {
for r := range server.incomingRequests {
received++
r.Reply(r.Type == "yes", nil)
}
wg.Done()
}()
_, err := client.SendRequest("yes", false, nil)
if err != nil {
t.Fatalf("SendRequest: %v", err)
}
ok, err := client.SendRequest("yes", true, nil)
if err != nil {
t.Fatalf("SendRequest: %v", err)
}
if !ok {
t.Errorf("SendRequest(yes): %v", ok)
}
ok, err = client.SendRequest("no", true, nil)
if err != nil {
t.Fatalf("SendRequest: %v", err)
}
if ok {
t.Errorf("SendRequest(no): %v", ok)
}
client.Close()
wg.Wait()
if received != 3 {
t.Errorf("got %d requests, want %d", received, 3)
}
}
func TestMuxGlobalRequest(t *testing.T) {
clientMux, serverMux := muxPair()
defer serverMux.Close()
defer clientMux.Close()
var seen bool
go func() {
for r := range serverMux.incomingRequests {
seen = seen || r.Type == "peek"
if r.WantReply {
err := r.Reply(r.Type == "yes",
append([]byte(r.Type), r.Payload...))
if err != nil {
t.Errorf("AckRequest: %v", err)
}
}
}
}()
_, _, err := clientMux.SendRequest("peek", false, nil)
if err != nil {
t.Errorf("SendRequest: %v", err)
}
ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
if !ok || string(data) != "yesa" || err != nil {
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
ok, data, err)
}
if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
ok, data, err)
}
if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
ok, data, err)
}
if !seen {
t.Errorf("never saw 'peek' request")
}
}
func TestMuxGlobalRequestUnblock(t *testing.T) {
clientMux, serverMux := muxPair()
defer serverMux.Close()
defer clientMux.Close()
result := make(chan error, 1)
go func() {
_, _, err := clientMux.SendRequest("hello", true, nil)
result <- err
}()
<-serverMux.incomingRequests
serverMux.conn.Close()
err := <-result
if err != io.EOF {
t.Errorf("want EOF, got %v", io.EOF)
}
}
func TestMuxChannelRequestUnblock(t *testing.T) {
a, b, connB := channelPair(t)
defer a.Close()
defer b.Close()
defer connB.Close()
result := make(chan error, 1)
go func() {
_, err := a.SendRequest("hello", true, nil)
result <- err
}()
<-b.incomingRequests
connB.conn.Close()
err := <-result
if err != io.EOF {
t.Errorf("want EOF, got %v", err)
}
}
func TestMuxCloseChannel(t *testing.T) {
r, w, mux := channelPair(t)
defer mux.Close()
defer r.Close()
defer w.Close()
result := make(chan error, 1)
go func() {
var b [1024]byte
_, err := r.Read(b[:])
result <- err
}()
if err := w.Close(); err != nil {
t.Errorf("w.Close: %v", err)
}
if _, err := w.Write([]byte("hello")); err != io.EOF {
t.Errorf("got err %v, want io.EOF after Close", err)
}
if err := <-result; err != io.EOF {
t.Errorf("got %v (%T), want io.EOF", err, err)
}
}
func TestMuxCloseWriteChannel(t *testing.T) {
r, w, mux := channelPair(t)
defer mux.Close()
result := make(chan error, 1)
go func() {
var b [1024]byte
_, err := r.Read(b[:])
result <- err
}()
if err := w.CloseWrite(); err != nil {
t.Errorf("w.CloseWrite: %v", err)
}
if _, err := w.Write([]byte("hello")); err != io.EOF {
t.Errorf("got err %v, want io.EOF after CloseWrite", err)
}
if err := <-result; err != io.EOF {
t.Errorf("got %v (%T), want io.EOF", err, err)
}
}
func TestMuxInvalidRecord(t *testing.T) {
a, b := muxPair()
defer a.Close()
defer b.Close()
packet := make([]byte, 1+4+4+1)
packet[0] = msgChannelData
marshalUint32(packet[1:], 29348723 /* invalid channel id */)
marshalUint32(packet[5:], 1)
packet[9] = 42
a.conn.writePacket(packet)
go a.SendRequest("hello", false, nil)
// 'a' wrote an invalid packet, so 'b' has exited.
req, ok := <-b.incomingRequests
if ok {
t.Errorf("got request %#v after receiving invalid packet", req)
}
}
func TestZeroWindowAdjust(t *testing.T) {
a, b, mux := channelPair(t)
defer a.Close()
defer b.Close()
defer mux.Close()
go func() {
io.WriteString(a, "hello")
// bogus adjust.
a.sendMessage(windowAdjustMsg{})
io.WriteString(a, "world")
a.Close()
}()
want := "helloworld"
c, _ := ioutil.ReadAll(b)
if string(c) != want {
t.Errorf("got %q want %q", c, want)
}
}
func TestMuxMaxPacketSize(t *testing.T) {
a, b, mux := channelPair(t)
defer a.Close()
defer b.Close()
defer mux.Close()
large := make([]byte, a.maxRemotePayload+1)
packet := make([]byte, 1+4+4+1+len(large))
packet[0] = msgChannelData
marshalUint32(packet[1:], a.remoteId)
marshalUint32(packet[5:], uint32(len(large)))
packet[9] = 42
if err := a.mux.conn.writePacket(packet); err != nil {
t.Errorf("could not send packet")
}
go a.SendRequest("hello", false, nil)
_, ok := <-b.incomingRequests
if ok {
t.Errorf("connection still alive after receiving large packet.")
}
}
// Don't ship code with debug=true.
func TestDebug(t *testing.T) {
if debugMux {
t.Error("mux debug switched on")
}
if debugHandshake {
t.Error("handshake debug switched on")
}
if debugTransport {
t.Error("transport debug switched on")
}
}

View file

@ -95,6 +95,10 @@ type ServerConfig struct {
// Note that RFC 4253 section 4.2 requires that this string start with
// "SSH-2.0-".
ServerVersion string
// BannerCallback, if present, is called and the return string is sent to
// the client after key exchange completed but before authentication.
BannerCallback func(conn ConnMetadata) string
}
// AddHostKey adds a private key as a host key. If an existing host
@ -162,6 +166,9 @@ type ServerConn struct {
// unsuccessful, it closes the connection and returns an error. The
// Request and NewChannel channels must be serviced, or the connection
// will hang.
//
// The returned error may be of type *ServerAuthError for
// authentication errors.
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config
fullConf.SetDefaults()
@ -252,7 +259,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
func isAcceptableAlgo(algo string) bool {
switch algo {
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
return true
}
return false
@ -288,12 +295,13 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
}
// ServerAuthError implements the error interface. It appends any authentication
// errors that may occur, and is returned if all of the authentication methods
// provided by the user failed to authenticate.
// ServerAuthError represents server authentication errors and is
// sometimes returned by NewServerConn. It appends any authentication
// errors that may occur, and is returned if all of the authentication
// methods provided by the user failed to authenticate.
type ServerAuthError struct {
// Errors contains authentication errors returned by the authentication
// callback methods.
// callback methods. The first entry is typically ErrNoAuth.
Errors []error
}
@ -305,6 +313,13 @@ func (l ServerAuthError) Error() string {
return "[" + strings.Join(errs, ", ") + "]"
}
// ErrNoAuth is the error value returned if no
// authentication method has been passed yet. This happens as a normal
// part of the authentication loop, since the client first tries
// 'none' authentication to discover available methods.
// It is returned in ServerAuthError.Errors from NewServerConn.
var ErrNoAuth = errors.New("ssh: no auth passed yet")
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
sessionID := s.transport.getSessionID()
var cache pubKeyCache
@ -312,6 +327,7 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
authFailures := 0
var authErrs []error
var displayedBanner bool
userAuthLoop:
for {
@ -343,8 +359,22 @@ userAuthLoop:
}
s.user = userAuthReq.User
if !displayedBanner && config.BannerCallback != nil {
displayedBanner = true
msg := config.BannerCallback(s)
if msg != "" {
bannerMsg := &userAuthBannerMsg{
Message: msg,
}
if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
return nil, err
}
}
}
perms = nil
authErr := errors.New("no auth passed yet")
authErr := ErrNoAuth
switch userAuthReq.Method {
case "none":

View file

@ -406,7 +406,7 @@ func (s *Session) Wait() error {
s.stdinPipeWriter.Close()
}
var copyError error
for _ = range s.copyFuncs {
for range s.copyFuncs {
if err := <-s.errors; err != nil && copyError == nil {
copyError = err
}

View file

@ -1,774 +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.
package ssh
// Session tests.
import (
"bytes"
crypto_rand "crypto/rand"
"errors"
"io"
"io/ioutil"
"math/rand"
"net"
"testing"
"golang.org/x/crypto/ssh/terminal"
)
type serverType func(Channel, <-chan *Request, *testing.T)
// dial constructs a new test server and returns a *ClientConn.
func dial(handler serverType, t *testing.T) *Client {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
go func() {
defer c1.Close()
conf := ServerConfig{
NoClientAuth: true,
}
conf.AddHostKey(testSigners["rsa"])
_, chans, reqs, err := NewServerConn(c1, &conf)
if err != nil {
t.Fatalf("Unable to handshake: %v", err)
}
go DiscardRequests(reqs)
for newCh := range chans {
if newCh.ChannelType() != "session" {
newCh.Reject(UnknownChannelType, "unknown channel type")
continue
}
ch, inReqs, err := newCh.Accept()
if err != nil {
t.Errorf("Accept: %v", err)
continue
}
go func() {
handler(ch, inReqs, t)
}()
}
}()
config := &ClientConfig{
User: "testuser",
HostKeyCallback: InsecureIgnoreHostKey(),
}
conn, chans, reqs, err := NewClientConn(c2, "", config)
if err != nil {
t.Fatalf("unable to dial remote side: %v", err)
}
return NewClient(conn, chans, reqs)
}
// Test a simple string is returned to session.Stdout.
func TestSessionShell(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
stdout := new(bytes.Buffer)
session.Stdout = stdout
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %v", err)
}
actual := stdout.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
// Test a simple string is returned via StdoutPipe.
func TestSessionStdoutPipe(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("Unable to request StdoutPipe(): %v", err)
}
var buf bytes.Buffer
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
done := make(chan bool, 1)
go func() {
if _, err := io.Copy(&buf, stdout); err != nil {
t.Errorf("Copy of stdout failed: %v", err)
}
done <- true
}()
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %v", err)
}
<-done
actual := buf.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// Test that a simple string is returned via the Output helper,
// and that stderr is discarded.
func TestSessionOutput(t *testing.T) {
conn := dial(fixedOutputHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
if err != nil {
t.Error("Remote command did not exit cleanly:", err)
}
w := "this-is-stdout."
g := string(buf)
if g != w {
t.Error("Remote command did not return expected string:")
t.Logf("want %q", w)
t.Logf("got %q", g)
}
}
// Test that both stdout and stderr are returned
// via the CombinedOutput helper.
func TestSessionCombinedOutput(t *testing.T) {
conn := dial(fixedOutputHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
if err != nil {
t.Error("Remote command did not exit cleanly:", err)
}
const stdout = "this-is-stdout."
const stderr = "this-is-stderr."
g := string(buf)
if g != stdout+stderr && g != stderr+stdout {
t.Error("Remote command did not return expected string:")
t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
t.Logf("got %q", g)
}
}
// Test non-0 exit status is returned correctly.
func TestExitStatusNonZero(t *testing.T) {
conn := dial(exitStatusNonZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
}
}
// Test 0 exit status is returned correctly.
func TestExitStatusZero(t *testing.T) {
conn := dial(exitStatusZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
}
// Test exit signal and status are both returned correctly.
func TestExitSignalAndStatus(t *testing.T) {
conn := dial(exitSignalAndStatusHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestKnownExitSignalOnly(t *testing.T) {
conn := dial(exitSignalHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 143 {
t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestUnknownExitSignal(t *testing.T) {
conn := dial(exitSignalUnknownHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "SYS" || e.ExitStatus() != 128 {
t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
func TestExitWithoutStatusOrSignal(t *testing.T) {
conn := dial(exitWithoutSignalOrStatus, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %v", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
if _, ok := err.(*ExitMissingError); !ok {
t.Fatalf("got %T want *ExitMissingError", err)
}
}
// windowTestBytes is the number of bytes that we'll send to the SSH server.
const windowTestBytes = 16000 * 200
// TestServerWindow writes random data to the server. The server is expected to echo
// the same data back, which is compared against the original.
func TestServerWindow(t *testing.T) {
origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
origBytes := origBuf.Bytes()
conn := dial(echoHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatal(err)
}
defer session.Close()
result := make(chan []byte)
go func() {
defer close(result)
echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
serverStdout, err := session.StdoutPipe()
if err != nil {
t.Errorf("StdoutPipe failed: %v", err)
return
}
n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
if err != nil && err != io.EOF {
t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
}
result <- echoedBuf.Bytes()
}()
serverStdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe failed: %v", err)
}
written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
if err != nil {
t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
}
if written != windowTestBytes {
t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
}
echoedBytes := <-result
if !bytes.Equal(origBytes, echoedBytes) {
t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
}
}
// Verify the client can handle a keepalive packet from the server.
func TestClientHandlesKeepalives(t *testing.T) {
conn := dial(channelKeepaliveSender, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatal(err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got: %v", err)
}
}
type exitStatusMsg struct {
Status uint32
}
type exitSignalMsg struct {
Signal string
CoreDumped bool
Errmsg string
Lang string
}
func handleTerminalRequests(in <-chan *Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any commands, only the default shell.
ok = false
}
case "env":
ok = true
}
req.Reply(ok, nil)
}
}
func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
term := terminal.NewTerminal(ch, prompt)
go handleTerminalRequests(in)
return term
}
func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
sendStatus(0, ch, t)
}
func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
sendStatus(15, ch, t)
}
func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
sendStatus(15, ch, t)
sendSignal("TERM", ch, t)
}
func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
sendSignal("TERM", ch, t)
}
func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
sendSignal("SYS", ch, t)
}
func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
}
func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, in, "golang")
readLine(shell, t)
sendStatus(0, ch, t)
}
// Ignores the command, writes fixed strings to stderr and stdout.
// Strings are "this-is-stdout." and "this-is-stderr.".
func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
_, err := ch.Read(nil)
req, ok := <-in
if !ok {
t.Fatalf("error: expected channel request, got: %#v", err)
return
}
// ignore request, always send some text
req.Reply(true, nil)
_, err = io.WriteString(ch, "this-is-stdout.")
if err != nil {
t.Fatalf("error writing on server: %v", err)
}
_, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
if err != nil {
t.Fatalf("error writing on server: %v", err)
}
sendStatus(0, ch, t)
}
func readLine(shell *terminal.Terminal, t *testing.T) {
if _, err := shell.ReadLine(); err != nil && err != io.EOF {
t.Errorf("unable to read line: %v", err)
}
}
func sendStatus(status uint32, ch Channel, t *testing.T) {
msg := exitStatusMsg{
Status: status,
}
if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
t.Errorf("unable to send status: %v", err)
}
}
func sendSignal(signal string, ch Channel, t *testing.T) {
sig := exitSignalMsg{
Signal: signal,
CoreDumped: false,
Errmsg: "Process terminated",
Lang: "en-GB-oed",
}
if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
t.Errorf("unable to send signal: %v", err)
}
}
func discardHandler(ch Channel, t *testing.T) {
defer ch.Close()
io.Copy(ioutil.Discard, ch)
}
func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
}
}
// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
// buffer size to exercise more code paths.
func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
var (
buf = make([]byte, 32*1024)
written int
remaining = n
)
for remaining > 0 {
l := rand.Intn(1 << 15)
if remaining < l {
l = remaining
}
nr, er := src.Read(buf[:l])
nw, ew := dst.Write(buf[:nr])
remaining -= nw
written += nw
if ew != nil {
return written, ew
}
if nr != nw {
return written, io.ErrShortWrite
}
if er != nil && er != io.EOF {
return written, er
}
}
return written, nil
}
func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
shell := newServerShell(ch, in, "> ")
readLine(shell, t)
if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
t.Errorf("unable to send channel keepalive request: %v", err)
}
sendStatus(0, ch, t)
}
func TestClientWriteEOF(t *testing.T) {
conn := dial(simpleEchoHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatal(err)
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe failed: %v", err)
}
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("StdoutPipe failed: %v", err)
}
data := []byte(`0000`)
_, err = stdin.Write(data)
if err != nil {
t.Fatalf("Write failed: %v", err)
}
stdin.Close()
res, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatalf("Read failed: %v", err)
}
if !bytes.Equal(data, res) {
t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
}
}
func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
defer ch.Close()
data, err := ioutil.ReadAll(ch)
if err != nil {
t.Errorf("handler read error: %v", err)
}
_, err = ch.Write(data)
if err != nil {
t.Errorf("handler write error: %v", err)
}
}
func TestSessionID(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
serverID := make(chan []byte, 1)
clientID := make(chan []byte, 1)
serverConf := &ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["ecdsa"])
clientConf := &ClientConfig{
HostKeyCallback: InsecureIgnoreHostKey(),
User: "user",
}
go func() {
conn, chans, reqs, err := NewServerConn(c1, serverConf)
if err != nil {
t.Fatalf("server handshake: %v", err)
}
serverID <- conn.SessionID()
go DiscardRequests(reqs)
for ch := range chans {
ch.Reject(Prohibited, "")
}
}()
go func() {
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
if err != nil {
t.Fatalf("client handshake: %v", err)
}
clientID <- conn.SessionID()
go DiscardRequests(reqs)
for ch := range chans {
ch.Reject(Prohibited, "")
}
}()
s := <-serverID
c := <-clientID
if bytes.Compare(s, c) != 0 {
t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
} else if len(s) == 0 {
t.Errorf("client and server SessionID were empty.")
}
}
type noReadConn struct {
readSeen bool
net.Conn
}
func (c *noReadConn) Close() error {
return nil
}
func (c *noReadConn) Read(b []byte) (int, error) {
c.readSeen = true
return 0, errors.New("noReadConn error")
}
func TestInvalidServerConfiguration(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
serveConn := noReadConn{Conn: c1}
serverConf := &ServerConfig{}
NewServerConn(&serveConn, serverConf)
if serveConn.readSeen {
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
}
serverConf.AddHostKey(testSigners["ecdsa"])
NewServerConn(&serveConn, serverConf)
if serveConn.readSeen {
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
}
}
func TestHostKeyAlgorithms(t *testing.T) {
serverConf := &ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["rsa"])
serverConf.AddHostKey(testSigners["ecdsa"])
connect := func(clientConf *ClientConfig, want string) {
var alg string
clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
alg = key.Type()
return nil
}
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewServerConn(c1, serverConf)
_, _, _, err = NewClientConn(c2, "", clientConf)
if err != nil {
t.Fatalf("NewClientConn: %v", err)
}
if alg != want {
t.Errorf("selected key algorithm %s, want %s", alg, want)
}
}
// By default, we get the preferred algorithm, which is ECDSA 256.
clientConf := &ClientConfig{
HostKeyCallback: InsecureIgnoreHostKey(),
}
connect(clientConf, KeyAlgoECDSA256)
// Client asks for RSA explicitly.
clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
connect(clientConf, KeyAlgoRSA)
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
go NewServerConn(c1, serverConf)
clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
_, _, _, err = NewClientConn(c2, "", clientConf)
if err == nil {
t.Fatal("succeeded connecting with unknown hostkey algorithm")
}
}

View file

@ -32,6 +32,7 @@ type streamLocalChannelForwardMsg struct {
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
c.handleForwardsOnce.Do(c.handleForwards)
m := streamLocalChannelForwardMsg{
socketPath,
}

View file

@ -90,10 +90,19 @@ type channelForwardMsg struct {
rport uint32
}
// handleForwards starts goroutines handling forwarded connections.
// It's called on first use by (*Client).ListenTCP to not launch
// goroutines until needed.
func (c *Client) handleForwards() {
go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-tcpip"))
go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
}
// ListenTCP requests the remote peer open a listening socket
// on laddr. Incoming connections will be available by calling
// Accept on the returned net.Listener.
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
c.handleForwardsOnce.Do(c.handleForwards)
if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
return c.autoPortListenWorkaround(laddr)
}

View file

@ -1,20 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"testing"
)
func TestAutoPortListenBroken(t *testing.T) {
broken := "SSH-2.0-OpenSSH_5.9hh11"
works := "SSH-2.0-OpenSSH_6.1"
if !isBrokenOpenSSHVersion(broken) {
t.Errorf("version %q not marked as broken", broken)
}
if isBrokenOpenSSHVersion(works) {
t.Errorf("version %q marked as broken", works)
}
}

View file

@ -617,7 +617,7 @@ func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
if _, err = w.Write(crlf); err != nil {
return n, err
}
n += 1
n++
buf = buf[1:]
}
}

View file

@ -1,350 +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.
package terminal
import (
"bytes"
"io"
"os"
"testing"
)
type MockTerminal struct {
toSend []byte
bytesPerRead int
received []byte
}
func (c *MockTerminal) Read(data []byte) (n int, err error) {
n = len(data)
if n == 0 {
return
}
if n > len(c.toSend) {
n = len(c.toSend)
}
if n == 0 {
return 0, io.EOF
}
if c.bytesPerRead > 0 && n > c.bytesPerRead {
n = c.bytesPerRead
}
copy(data, c.toSend[:n])
c.toSend = c.toSend[n:]
return
}
func (c *MockTerminal) Write(data []byte) (n int, err error) {
c.received = append(c.received, data...)
return len(data), nil
}
func TestClose(t *testing.T) {
c := &MockTerminal{}
ss := NewTerminal(c, "> ")
line, err := ss.ReadLine()
if line != "" {
t.Errorf("Expected empty line but got: %s", line)
}
if err != io.EOF {
t.Errorf("Error should have been EOF but got: %s", err)
}
}
var keyPressTests = []struct {
in string
line string
err error
throwAwayLines int
}{
{
err: io.EOF,
},
{
in: "\r",
line: "",
},
{
in: "foo\r",
line: "foo",
},
{
in: "a\x1b[Cb\r", // right
line: "ab",
},
{
in: "a\x1b[Db\r", // left
line: "ba",
},
{
in: "a\177b\r", // backspace
line: "b",
},
{
in: "\x1b[A\r", // up
},
{
in: "\x1b[B\r", // down
},
{
in: "line\x1b[A\x1b[B\r", // up then down
line: "line",
},
{
in: "line1\rline2\x1b[A\r", // recall previous line.
line: "line1",
throwAwayLines: 1,
},
{
// recall two previous lines and append.
in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
line: "line1xxx",
throwAwayLines: 2,
},
{
// Ctrl-A to move to beginning of line followed by ^K to kill
// line.
in: "a b \001\013\r",
line: "",
},
{
// Ctrl-A to move to beginning of line, Ctrl-E to move to end,
// finally ^K to kill nothing.
in: "a b \001\005\013\r",
line: "a b ",
},
{
in: "\027\r",
line: "",
},
{
in: "a\027\r",
line: "",
},
{
in: "a \027\r",
line: "",
},
{
in: "a b\027\r",
line: "a ",
},
{
in: "a b \027\r",
line: "a ",
},
{
in: "one two thr\x1b[D\027\r",
line: "one two r",
},
{
in: "\013\r",
line: "",
},
{
in: "a\013\r",
line: "a",
},
{
in: "ab\x1b[D\013\r",
line: "a",
},
{
in: "Ξεσκεπάζω\r",
line: "Ξεσκεπάζω",
},
{
in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace.
line: "",
throwAwayLines: 1,
},
{
in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
line: "£",
throwAwayLines: 1,
},
{
// Ctrl-D at the end of the line should be ignored.
in: "a\004\r",
line: "a",
},
{
// a, b, left, Ctrl-D should erase the b.
in: "ab\x1b[D\004\r",
line: "a",
},
{
// a, b, c, d, left, left, ^U should erase to the beginning of
// the line.
in: "abcd\x1b[D\x1b[D\025\r",
line: "cd",
},
{
// Bracketed paste mode: control sequences should be returned
// verbatim in paste mode.
in: "abc\x1b[200~de\177f\x1b[201~\177\r",
line: "abcde\177",
},
{
// Enter in bracketed paste mode should still work.
in: "abc\x1b[200~d\refg\x1b[201~h\r",
line: "efgh",
throwAwayLines: 1,
},
{
// Lines consisting entirely of pasted data should be indicated as such.
in: "\x1b[200~a\r",
line: "a",
err: ErrPasteIndicator,
},
}
func TestKeyPresses(t *testing.T) {
for i, test := range keyPressTests {
for j := 1; j < len(test.in); j++ {
c := &MockTerminal{
toSend: []byte(test.in),
bytesPerRead: j,
}
ss := NewTerminal(c, "> ")
for k := 0; k < test.throwAwayLines; k++ {
_, err := ss.ReadLine()
if err != nil {
t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
}
}
line, err := ss.ReadLine()
if line != test.line {
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
break
}
if err != test.err {
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
break
}
}
}
}
func TestPasswordNotSaved(t *testing.T) {
c := &MockTerminal{
toSend: []byte("password\r\x1b[A\r"),
bytesPerRead: 1,
}
ss := NewTerminal(c, "> ")
pw, _ := ss.ReadPassword("> ")
if pw != "password" {
t.Fatalf("failed to read password, got %s", pw)
}
line, _ := ss.ReadLine()
if len(line) > 0 {
t.Fatalf("password was saved in history")
}
}
var setSizeTests = []struct {
width, height int
}{
{40, 13},
{80, 24},
{132, 43},
}
func TestTerminalSetSize(t *testing.T) {
for _, setSize := range setSizeTests {
c := &MockTerminal{
toSend: []byte("password\r\x1b[A\r"),
bytesPerRead: 1,
}
ss := NewTerminal(c, "> ")
ss.SetSize(setSize.width, setSize.height)
pw, _ := ss.ReadPassword("Password: ")
if pw != "password" {
t.Fatalf("failed to read password, got %s", pw)
}
if string(c.received) != "Password: \r\n" {
t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
}
}
}
func TestReadPasswordLineEnd(t *testing.T) {
var tests = []struct {
input string
want string
}{
{"\n", ""},
{"\r\n", ""},
{"test\r\n", "test"},
{"testtesttesttes\n", "testtesttesttes"},
{"testtesttesttes\r\n", "testtesttesttes"},
{"testtesttesttesttest\n", "testtesttesttesttest"},
{"testtesttesttesttest\r\n", "testtesttesttesttest"},
}
for _, test := range tests {
buf := new(bytes.Buffer)
if _, err := buf.WriteString(test.input); err != nil {
t.Fatal(err)
}
have, err := readPasswordLine(buf)
if err != nil {
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
continue
}
if string(have) != test.want {
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
continue
}
if _, err = buf.WriteString(test.input); err != nil {
t.Fatal(err)
}
have, err = readPasswordLine(buf)
if err != nil {
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
continue
}
if string(have) != test.want {
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
continue
}
}
}
func TestMakeRawState(t *testing.T) {
fd := int(os.Stdout.Fd())
if !IsTerminal(fd) {
t.Skip("stdout is not a terminal; skipping test")
}
st, err := GetState(fd)
if err != nil {
t.Fatalf("failed to get terminal state from GetState: %s", err)
}
defer Restore(fd, st)
raw, err := MakeRaw(fd)
if err != nil {
t.Fatalf("failed to get terminal state from MakeRaw: %s", err)
}
if *st != *raw {
t.Errorf("states do not match; was %v, expected %v", raw, st)
}
}
func TestOutputNewlines(t *testing.T) {
// \n should be changed to \r\n in terminal output.
buf := new(bytes.Buffer)
term := NewTerminal(buf, ">")
term.Write([]byte("1\n2\n"))
output := string(buf.Bytes())
const expected = "1\r\n2\r\n"
if output != expected {
t.Errorf("incorrect output: was %q, expected %q", output, expected)
}
}

View file

@ -108,9 +108,7 @@ func ReadPassword(fd int) ([]byte, error) {
return nil, err
}
defer func() {
unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
}()
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
return readPasswordLine(passwordReader(fd))
}

View file

@ -14,7 +14,7 @@ import (
// State contains the state of a terminal.
type State struct {
state *unix.Termios
termios unix.Termios
}
// IsTerminal returns true if the given file descriptor is a terminal.
@ -75,47 +75,43 @@ func ReadPassword(fd int) ([]byte, error) {
// restored.
// see http://cr.illumos.org/~webrev/andy_js/1060/
func MakeRaw(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
oldTermios := *oldTermiosPtr
newTermios := oldTermios
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
newTermios.Oflag &^= syscall.OPOST
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
newTermios.Cflag |= syscall.CS8
newTermios.Cc[unix.VMIN] = 1
newTermios.Cc[unix.VTIME] = 0
oldState := State{termios: *termios}
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
termios.Oflag &^= unix.OPOST
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
termios.Cflag &^= unix.CSIZE | unix.PARENB
termios.Cflag |= unix.CS8
termios.Cc[unix.VMIN] = 1
termios.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
return &oldState, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, oldState *State) error {
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
return &State{termios: *termios}, nil
}
// GetSize returns the dimensions of the given terminal.

View file

@ -17,6 +17,8 @@
package terminal
import (
"os"
"golang.org/x/sys/windows"
)
@ -71,13 +73,6 @@ func GetSize(fd int) (width, height int, err error) {
return int(info.Size.X), int(info.Size.Y), nil
}
// passwordReader is an io.Reader that reads from a specific Windows HANDLE.
type passwordReader int
func (r passwordReader) Read(buf []byte) (int, error) {
return windows.Read(windows.Handle(r), buf)
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
@ -94,9 +89,15 @@ func ReadPassword(fd int) ([]byte, error) {
return nil, err
}
defer func() {
windows.SetConsoleMode(windows.Handle(fd), old)
}()
defer windows.SetConsoleMode(windows.Handle(fd), old)
return readPasswordLine(passwordReader(fd))
var h windows.Handle
p, _ := windows.GetCurrentProcess()
if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
return nil, err
}
f := os.NewFile(uintptr(h), "stdin")
defer f.Close()
return readPasswordLine(f)
}

View file

@ -1,63 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
// instances.
package ssh
import (
"crypto/rand"
"fmt"
"golang.org/x/crypto/ssh/testdata"
)
var (
testPrivateKeys map[string]interface{}
testSigners map[string]Signer
testPublicKeys map[string]PublicKey
)
func init() {
var err error
n := len(testdata.PEMBytes)
testPrivateKeys = make(map[string]interface{}, n)
testSigners = make(map[string]Signer, n)
testPublicKeys = make(map[string]PublicKey, n)
for t, k := range testdata.PEMBytes {
testPrivateKeys[t], err = ParseRawPrivateKey(k)
if err != nil {
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
}
testSigners[t], err = NewSignerFromKey(testPrivateKeys[t])
if err != nil {
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
}
testPublicKeys[t] = testSigners[t].PublicKey()
}
// Create a cert and sign it for use in tests.
testCert := &Certificate{
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
ValidAfter: 0, // unix epoch
ValidBefore: CertTimeInfinity, // The end of currently representable time.
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
Key: testPublicKeys["ecdsa"],
SignatureKey: testPublicKeys["rsa"],
Permissions: Permissions{
CriticalOptions: map[string]string{},
Extensions: map[string]string{},
},
}
testCert.SignCert(rand.Reader, testSigners["rsa"])
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
testSigners["cert"], err = NewCertSigner(testCert, testSigners["ecdsa"])
if err != nil {
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
}
}

View file

@ -6,6 +6,7 @@ package ssh
import (
"bufio"
"bytes"
"errors"
"io"
"log"
@ -76,17 +77,17 @@ type connectionState struct {
// both directions are triggered by reading and writing a msgNewKey packet
// respectively.
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult)
if err != nil {
return err
} else {
t.reader.pendingKeyChange <- ciph
}
t.reader.pendingKeyChange <- ciph
if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult)
if err != nil {
return err
} else {
t.writer.pendingKeyChange <- ciph
}
t.writer.pendingKeyChange <- ciph
return nil
}
@ -139,7 +140,7 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
case cipher := <-s.pendingKeyChange:
s.packetCipher = cipher
default:
return nil, errors.New("ssh: got bogus newkeys message.")
return nil, errors.New("ssh: got bogus newkeys message")
}
case msgDisconnect:
@ -232,52 +233,22 @@ var (
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
)
// generateKeys generates key material for IV, MAC and encryption.
func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
cipherMode := cipherModes[algs.Cipher]
macMode := macModes[algs.MAC]
iv = make([]byte, cipherMode.ivSize)
key = make([]byte, cipherMode.keySize)
macKey = make([]byte, macMode.keySize)
generateKeyMaterial(iv, d.ivTag, kex)
generateKeyMaterial(key, d.keyTag, kex)
generateKeyMaterial(macKey, d.macKeyTag, kex)
return
}
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
// described in RFC 4253, section 6.4. direction should either be serverKeys
// (to setup server->client keys) or clientKeys (for client->server keys).
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
iv, key, macKey := generateKeys(d, algs, kex)
cipherMode := cipherModes[algs.Cipher]
macMode := macModes[algs.MAC]
if algs.Cipher == gcmCipherID {
return newGCMCipher(iv, key)
}
iv := make([]byte, cipherMode.ivSize)
key := make([]byte, cipherMode.keySize)
macKey := make([]byte, macMode.keySize)
if algs.Cipher == aes128cbcID {
return newAESCBCCipher(iv, key, macKey, algs)
}
generateKeyMaterial(iv, d.ivTag, kex)
generateKeyMaterial(key, d.keyTag, kex)
generateKeyMaterial(macKey, d.macKeyTag, kex)
if algs.Cipher == tripledescbcID {
return newTripleDESCBCCipher(iv, key, macKey, algs)
}
c := &streamPacketCipher{
mac: macModes[algs.MAC].new(macKey),
etm: macModes[algs.MAC].etm,
}
c.macResult = make([]byte, c.mac.Size())
var err error
c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
if err != nil {
return nil, err
}
return c, nil
return cipherModes[algs.Cipher].create(key, iv, macKey, algs)
}
// generateKeyMaterial fills out with key material generated from tag, K, H
@ -342,7 +313,7 @@ func readVersion(r io.Reader) ([]byte, error) {
var ok bool
var buf [1]byte
for len(versionString) < maxVersionStringBytes {
for length := 0; length < maxVersionStringBytes; length++ {
_, err := io.ReadFull(r, buf[:])
if err != nil {
return nil, err
@ -350,6 +321,13 @@ func readVersion(r io.Reader) ([]byte, error) {
// The RFC says that the version should be terminated with \r\n
// but several SSH servers actually only send a \n.
if buf[0] == '\n' {
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
// RFC 4253 says we need to ignore all version string lines
// except the one containing the SSH version (provided that
// all the lines do not exceed 255 bytes in total).
versionString = versionString[:0]
continue
}
ok = true
break
}

View file

@ -1,109 +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.
package ssh
import (
"bytes"
"crypto/rand"
"encoding/binary"
"strings"
"testing"
)
func TestReadVersion(t *testing.T) {
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
cases := map[string]string{
"SSH-2.0-bla\r\n": "SSH-2.0-bla",
"SSH-2.0-bla\n": "SSH-2.0-bla",
longversion + "\r\n": longversion,
}
for in, want := range cases {
result, err := readVersion(bytes.NewBufferString(in))
if err != nil {
t.Errorf("readVersion(%q): %s", in, err)
}
got := string(result)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}
func TestReadVersionError(t *testing.T) {
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
cases := []string{
longversion + "too-long\r\n",
}
for _, in := range cases {
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
t.Errorf("readVersion(%q) should have failed", in)
}
}
}
func TestExchangeVersionsBasic(t *testing.T) {
v := "SSH-2.0-bla"
buf := bytes.NewBufferString(v + "\r\n")
them, err := exchangeVersions(buf, []byte("xyz"))
if err != nil {
t.Errorf("exchangeVersions: %v", err)
}
if want := "SSH-2.0-bla"; string(them) != want {
t.Errorf("got %q want %q for our version", them, want)
}
}
func TestExchangeVersions(t *testing.T) {
cases := []string{
"not\x000allowed",
"not allowed\n",
}
for _, c := range cases {
buf := bytes.NewBufferString("SSH-2.0-bla\r\n")
if _, err := exchangeVersions(buf, []byte(c)); err == nil {
t.Errorf("exchangeVersions(%q): should have failed", c)
}
}
}
type closerBuffer struct {
bytes.Buffer
}
func (b *closerBuffer) Close() error {
return nil
}
func TestTransportMaxPacketWrite(t *testing.T) {
buf := &closerBuffer{}
tr := newTransport(buf, rand.Reader, true)
huge := make([]byte, maxPacket+1)
err := tr.writePacket(huge)
if err == nil {
t.Errorf("transport accepted write for a huge packet.")
}
}
func TestTransportMaxPacketReader(t *testing.T) {
var header [5]byte
huge := make([]byte, maxPacket+128)
binary.BigEndian.PutUint32(header[0:], uint32(len(huge)))
// padding.
header[4] = 0
buf := &closerBuffer{}
buf.Write(header[:])
buf.Write(huge)
tr := newTransport(buf, rand.Reader, true)
_, err := tr.readPacket()
if err == nil {
t.Errorf("transport succeeded reading huge packet.")
} else if !strings.Contains(err.Error(), "large") {
t.Errorf("got %q, should mention %q", err.Error(), "large")
}
}

View file

@ -4,16 +4,15 @@ Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
@ -23,9 +22,5 @@ The gophers there will answer or ask you to file an issue if you've tripped over
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
**We do not accept GitHub pull requests**
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.

View file

@ -1,583 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.7
package context
import (
"fmt"
"math/rand"
"runtime"
"strings"
"sync"
"testing"
"time"
)
// otherContext is a Context that's not one of the types defined in context.go.
// This lets us test code paths that differ based on the underlying type of the
// Context.
type otherContext struct {
Context
}
func TestBackground(t *testing.T) {
c := Background()
if c == nil {
t.Fatalf("Background returned nil")
}
select {
case x := <-c.Done():
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
default:
}
if got, want := fmt.Sprint(c), "context.Background"; got != want {
t.Errorf("Background().String() = %q want %q", got, want)
}
}
func TestTODO(t *testing.T) {
c := TODO()
if c == nil {
t.Fatalf("TODO returned nil")
}
select {
case x := <-c.Done():
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
default:
}
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
t.Errorf("TODO().String() = %q want %q", got, want)
}
}
func TestWithCancel(t *testing.T) {
c1, cancel := WithCancel(Background())
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
t.Errorf("c1.String() = %q want %q", got, want)
}
o := otherContext{c1}
c2, _ := WithCancel(o)
contexts := []Context{c1, o, c2}
for i, c := range contexts {
if d := c.Done(); d == nil {
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
}
if e := c.Err(); e != nil {
t.Errorf("c[%d].Err() == %v want nil", i, e)
}
select {
case x := <-c.Done():
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
default:
}
}
cancel()
time.Sleep(100 * time.Millisecond) // let cancelation propagate
for i, c := range contexts {
select {
case <-c.Done():
default:
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
}
if e := c.Err(); e != Canceled {
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
}
}
}
func TestParentFinishesChild(t *testing.T) {
// Context tree:
// parent -> cancelChild
// parent -> valueChild -> timerChild
parent, cancel := WithCancel(Background())
cancelChild, stop := WithCancel(parent)
defer stop()
valueChild := WithValue(parent, "key", "value")
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
defer stop()
select {
case x := <-parent.Done():
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
case x := <-cancelChild.Done():
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
case x := <-timerChild.Done():
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
case x := <-valueChild.Done():
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
default:
}
// The parent's children should contain the two cancelable children.
pc := parent.(*cancelCtx)
cc := cancelChild.(*cancelCtx)
tc := timerChild.(*timerCtx)
pc.mu.Lock()
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
pc.children, cc, tc)
}
pc.mu.Unlock()
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
}
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
}
cancel()
pc.mu.Lock()
if len(pc.children) != 0 {
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
}
pc.mu.Unlock()
// parent and children should all be finished.
check := func(ctx Context, name string) {
select {
case <-ctx.Done():
default:
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
}
if e := ctx.Err(); e != Canceled {
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
}
}
check(parent, "parent")
check(cancelChild, "cancelChild")
check(valueChild, "valueChild")
check(timerChild, "timerChild")
// WithCancel should return a canceled context on a canceled parent.
precanceledChild := WithValue(parent, "key", "value")
select {
case <-precanceledChild.Done():
default:
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
}
if e := precanceledChild.Err(); e != Canceled {
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
}
}
func TestChildFinishesFirst(t *testing.T) {
cancelable, stop := WithCancel(Background())
defer stop()
for _, parent := range []Context{Background(), cancelable} {
child, cancel := WithCancel(parent)
select {
case x := <-parent.Done():
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
case x := <-child.Done():
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
default:
}
cc := child.(*cancelCtx)
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
}
if pcok {
pc.mu.Lock()
if len(pc.children) != 1 || !pc.children[cc] {
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
}
pc.mu.Unlock()
}
cancel()
if pcok {
pc.mu.Lock()
if len(pc.children) != 0 {
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
}
pc.mu.Unlock()
}
// child should be finished.
select {
case <-child.Done():
default:
t.Errorf("<-child.Done() blocked, but shouldn't have")
}
if e := child.Err(); e != Canceled {
t.Errorf("child.Err() == %v want %v", e, Canceled)
}
// parent should not be finished.
select {
case x := <-parent.Done():
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
default:
}
if e := parent.Err(); e != nil {
t.Errorf("parent.Err() == %v want nil", e)
}
}
}
func testDeadline(c Context, wait time.Duration, t *testing.T) {
select {
case <-time.After(wait):
t.Fatalf("context should have timed out")
case <-c.Done():
}
if e := c.Err(); e != DeadlineExceeded {
t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
}
}
func TestDeadline(t *testing.T) {
t.Parallel()
const timeUnit = 500 * time.Millisecond
c, _ := WithDeadline(Background(), time.Now().Add(1*timeUnit))
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
t.Errorf("c.String() = %q want prefix %q", got, prefix)
}
testDeadline(c, 2*timeUnit, t)
c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
o := otherContext{c}
testDeadline(o, 2*timeUnit, t)
c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
o = otherContext{c}
c, _ = WithDeadline(o, time.Now().Add(3*timeUnit))
testDeadline(c, 2*timeUnit, t)
}
func TestTimeout(t *testing.T) {
t.Parallel()
const timeUnit = 500 * time.Millisecond
c, _ := WithTimeout(Background(), 1*timeUnit)
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
t.Errorf("c.String() = %q want prefix %q", got, prefix)
}
testDeadline(c, 2*timeUnit, t)
c, _ = WithTimeout(Background(), 1*timeUnit)
o := otherContext{c}
testDeadline(o, 2*timeUnit, t)
c, _ = WithTimeout(Background(), 1*timeUnit)
o = otherContext{c}
c, _ = WithTimeout(o, 3*timeUnit)
testDeadline(c, 2*timeUnit, t)
}
func TestCanceledTimeout(t *testing.T) {
t.Parallel()
const timeUnit = 500 * time.Millisecond
c, _ := WithTimeout(Background(), 2*timeUnit)
o := otherContext{c}
c, cancel := WithTimeout(o, 4*timeUnit)
cancel()
time.Sleep(1 * timeUnit) // let cancelation propagate
select {
case <-c.Done():
default:
t.Errorf("<-c.Done() blocked, but shouldn't have")
}
if e := c.Err(); e != Canceled {
t.Errorf("c.Err() == %v want %v", e, Canceled)
}
}
type key1 int
type key2 int
var k1 = key1(1)
var k2 = key2(1) // same int as k1, different type
var k3 = key2(3) // same type as k2, different int
func TestValues(t *testing.T) {
check := func(c Context, nm, v1, v2, v3 string) {
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
}
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
}
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
}
}
c0 := Background()
check(c0, "c0", "", "", "")
c1 := WithValue(Background(), k1, "c1k1")
check(c1, "c1", "c1k1", "", "")
if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
t.Errorf("c.String() = %q want %q", got, want)
}
c2 := WithValue(c1, k2, "c2k2")
check(c2, "c2", "c1k1", "c2k2", "")
c3 := WithValue(c2, k3, "c3k3")
check(c3, "c2", "c1k1", "c2k2", "c3k3")
c4 := WithValue(c3, k1, nil)
check(c4, "c4", "", "c2k2", "c3k3")
o0 := otherContext{Background()}
check(o0, "o0", "", "", "")
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
check(o1, "o1", "c1k1", "", "")
o2 := WithValue(o1, k2, "o2k2")
check(o2, "o2", "c1k1", "o2k2", "")
o3 := otherContext{c4}
check(o3, "o3", "", "c2k2", "c3k3")
o4 := WithValue(o3, k3, nil)
check(o4, "o4", "", "c2k2", "")
}
func TestAllocs(t *testing.T) {
bg := Background()
for _, test := range []struct {
desc string
f func()
limit float64
gccgoLimit float64
}{
{
desc: "Background()",
f: func() { Background() },
limit: 0,
gccgoLimit: 0,
},
{
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
f: func() {
c := WithValue(bg, k1, nil)
c.Value(k1)
},
limit: 3,
gccgoLimit: 3,
},
{
desc: "WithTimeout(bg, 15*time.Millisecond)",
f: func() {
c, _ := WithTimeout(bg, 15*time.Millisecond)
<-c.Done()
},
limit: 8,
gccgoLimit: 16,
},
{
desc: "WithCancel(bg)",
f: func() {
c, cancel := WithCancel(bg)
cancel()
<-c.Done()
},
limit: 5,
gccgoLimit: 8,
},
{
desc: "WithTimeout(bg, 100*time.Millisecond)",
f: func() {
c, cancel := WithTimeout(bg, 100*time.Millisecond)
cancel()
<-c.Done()
},
limit: 8,
gccgoLimit: 25,
},
} {
limit := test.limit
if runtime.Compiler == "gccgo" {
// gccgo does not yet do escape analysis.
// TODO(iant): Remove this when gccgo does do escape analysis.
limit = test.gccgoLimit
}
if n := testing.AllocsPerRun(100, test.f); n > limit {
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
}
}
}
func TestSimultaneousCancels(t *testing.T) {
root, cancel := WithCancel(Background())
m := map[Context]CancelFunc{root: cancel}
q := []Context{root}
// Create a tree of contexts.
for len(q) != 0 && len(m) < 100 {
parent := q[0]
q = q[1:]
for i := 0; i < 4; i++ {
ctx, cancel := WithCancel(parent)
m[ctx] = cancel
q = append(q, ctx)
}
}
// Start all the cancels in a random order.
var wg sync.WaitGroup
wg.Add(len(m))
for _, cancel := range m {
go func(cancel CancelFunc) {
cancel()
wg.Done()
}(cancel)
}
// Wait on all the contexts in a random order.
for ctx := range m {
select {
case <-ctx.Done():
case <-time.After(1 * time.Second):
buf := make([]byte, 10<<10)
n := runtime.Stack(buf, true)
t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
}
}
// Wait for all the cancel functions to return.
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(1 * time.Second):
buf := make([]byte, 10<<10)
n := runtime.Stack(buf, true)
t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
}
}
func TestInterlockedCancels(t *testing.T) {
parent, cancelParent := WithCancel(Background())
child, cancelChild := WithCancel(parent)
go func() {
parent.Done()
cancelChild()
}()
cancelParent()
select {
case <-child.Done():
case <-time.After(1 * time.Second):
buf := make([]byte, 10<<10)
n := runtime.Stack(buf, true)
t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
}
}
func TestLayersCancel(t *testing.T) {
testLayers(t, time.Now().UnixNano(), false)
}
func TestLayersTimeout(t *testing.T) {
testLayers(t, time.Now().UnixNano(), true)
}
func testLayers(t *testing.T, seed int64, testTimeout bool) {
rand.Seed(seed)
errorf := func(format string, a ...interface{}) {
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
}
const (
timeout = 200 * time.Millisecond
minLayers = 30
)
type value int
var (
vals []*value
cancels []CancelFunc
numTimers int
ctx = Background()
)
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
switch rand.Intn(3) {
case 0:
v := new(value)
ctx = WithValue(ctx, v, v)
vals = append(vals, v)
case 1:
var cancel CancelFunc
ctx, cancel = WithCancel(ctx)
cancels = append(cancels, cancel)
case 2:
var cancel CancelFunc
ctx, cancel = WithTimeout(ctx, timeout)
cancels = append(cancels, cancel)
numTimers++
}
}
checkValues := func(when string) {
for _, key := range vals {
if val := ctx.Value(key).(*value); key != val {
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
}
}
}
select {
case <-ctx.Done():
errorf("ctx should not be canceled yet")
default:
}
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
}
t.Log(ctx)
checkValues("before cancel")
if testTimeout {
select {
case <-ctx.Done():
case <-time.After(timeout + 100*time.Millisecond):
errorf("ctx should have timed out")
}
checkValues("after timeout")
} else {
cancel := cancels[rand.Intn(len(cancels))]
cancel()
select {
case <-ctx.Done():
default:
errorf("ctx should be canceled")
}
checkValues("after cancel")
}
}
func TestCancelRemoves(t *testing.T) {
checkChildren := func(when string, ctx Context, want int) {
if got := len(ctx.(*cancelCtx).children); got != want {
t.Errorf("%s: context has %d children, want %d", when, got, want)
}
}
ctx, _ := WithCancel(Background())
checkChildren("after creation", ctx, 0)
_, cancel := WithCancel(ctx)
checkChildren("with WithCancel child ", ctx, 1)
cancel()
checkChildren("after cancelling WithCancel child", ctx, 0)
ctx, _ = WithCancel(Background())
checkChildren("after creation", ctx, 0)
_, cancel = WithTimeout(ctx, 60*time.Minute)
checkChildren("with WithTimeout child ", ctx, 1)
cancel()
checkChildren("after cancelling WithTimeout child", ctx, 0)
}

View file

@ -1,29 +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 !plan9,go1.7
package ctxhttp
import (
"io"
"net/http"
"net/http/httptest"
"testing"
"context"
)
func TestGo17Context(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "ok")
}))
defer ts.Close()
ctx := context.Background()
resp, err := Get(ctx, http.DefaultClient, ts.URL)
if resp == nil || err != nil {
t.Fatalf("error received from client: %v %v", err, resp)
}
resp.Body.Close()
}

View file

@ -1,79 +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 !plan9,!go1.7
package ctxhttp
import (
"net"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"golang.org/x/net/context"
)
// golang.org/issue/14065
func TestClosesResponseBodyOnCancel(t *testing.T) {
defer func() { testHookContextDoneBeforeHeaders = nop }()
defer func() { testHookDoReturned = nop }()
defer func() { testHookDidBodyClose = nop }()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer ts.Close()
ctx, cancel := context.WithCancel(context.Background())
// closed when Do enters select case <-ctx.Done()
enteredDonePath := make(chan struct{})
testHookContextDoneBeforeHeaders = func() {
close(enteredDonePath)
}
testHookDoReturned = func() {
// We now have the result (the Flush'd headers) at least,
// so we can cancel the request.
cancel()
// But block the client.Do goroutine from sending
// until Do enters into the <-ctx.Done() path, since
// otherwise if both channels are readable, select
// picks a random one.
<-enteredDonePath
}
sawBodyClose := make(chan struct{})
testHookDidBodyClose = func() { close(sawBodyClose) }
tr := &http.Transport{}
defer tr.CloseIdleConnections()
c := &http.Client{Transport: tr}
req, _ := http.NewRequest("GET", ts.URL, nil)
_, doErr := Do(ctx, c, req)
select {
case <-sawBodyClose:
case <-time.After(5 * time.Second):
t.Fatal("timeout waiting for body to close")
}
if doErr != ctx.Err() {
t.Errorf("Do error = %v; want %v", doErr, ctx.Err())
}
}
type noteCloseConn struct {
net.Conn
onceClose sync.Once
closefn func()
}
func (c *noteCloseConn) Close() error {
c.onceClose.Do(c.closefn)
return c.Conn.Close()
}

View file

@ -1,105 +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 !plan9
package ctxhttp
import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"golang.org/x/net/context"
)
const (
requestDuration = 100 * time.Millisecond
requestBody = "ok"
)
func okHandler(w http.ResponseWriter, r *http.Request) {
time.Sleep(requestDuration)
io.WriteString(w, requestBody)
}
func TestNoTimeout(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(okHandler))
defer ts.Close()
ctx := context.Background()
res, err := Get(ctx, nil, ts.URL)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if string(slurp) != requestBody {
t.Errorf("body = %q; want %q", slurp, requestBody)
}
}
func TestCancelBeforeHeaders(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
blockServer := make(chan struct{})
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cancel()
<-blockServer
io.WriteString(w, requestBody)
}))
defer ts.Close()
defer close(blockServer)
res, err := Get(ctx, nil, ts.URL)
if err == nil {
res.Body.Close()
t.Fatal("Get returned unexpected nil error")
}
if err != context.Canceled {
t.Errorf("err = %v; want %v", err, context.Canceled)
}
}
func TestCancelAfterHangingRequest(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
<-w.(http.CloseNotifier).CloseNotify()
}))
defer ts.Close()
ctx, cancel := context.WithCancel(context.Background())
resp, err := Get(ctx, nil, ts.URL)
if err != nil {
t.Fatalf("unexpected error in Get: %v", err)
}
// Cancel befer reading the body.
// Reading Request.Body should fail, since the request was
// canceled before anything was written.
cancel()
done := make(chan struct{})
go func() {
b, err := ioutil.ReadAll(resp.Body)
if len(b) != 0 || err == nil {
t.Errorf(`Read got (%q, %v); want ("", error)`, b, err)
}
close(done)
}()
select {
case <-time.After(1 * time.Second):
t.Errorf("Test timed out")
case <-done:
}
}

View file

@ -1,31 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package context_test
import (
"fmt"
"time"
"golang.org/x/net/context"
)
// This example passes a context with a timeout to tell a blocking function that
// it should abandon its work after the timeout elapses.
func ExampleWithTimeout() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
// Output:
// context deadline exceeded
}

View file

@ -1,109 +0,0 @@
// Copyright 2012 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 atom
import (
"sort"
"testing"
)
func TestKnown(t *testing.T) {
for _, s := range testAtomList {
if atom := Lookup([]byte(s)); atom.String() != s {
t.Errorf("Lookup(%q) = %#x (%q)", s, uint32(atom), atom.String())
}
}
}
func TestHits(t *testing.T) {
for _, a := range table {
if a == 0 {
continue
}
got := Lookup([]byte(a.String()))
if got != a {
t.Errorf("Lookup(%q) = %#x, want %#x", a.String(), uint32(got), uint32(a))
}
}
}
func TestMisses(t *testing.T) {
testCases := []string{
"",
"\x00",
"\xff",
"A",
"DIV",
"Div",
"dIV",
"aa",
"a\x00",
"ab",
"abb",
"abbr0",
"abbr ",
" abbr",
" a",
"acceptcharset",
"acceptCharset",
"accept_charset",
"h0",
"h1h2",
"h7",
"onClick",
"λ",
// The following string has the same hash (0xa1d7fab7) as "onmouseover".
"\x00\x00\x00\x00\x00\x50\x18\xae\x38\xd0\xb7",
}
for _, tc := range testCases {
got := Lookup([]byte(tc))
if got != 0 {
t.Errorf("Lookup(%q): got %d, want 0", tc, got)
}
}
}
func TestForeignObject(t *testing.T) {
const (
afo = Foreignobject
afO = ForeignObject
sfo = "foreignobject"
sfO = "foreignObject"
)
if got := Lookup([]byte(sfo)); got != afo {
t.Errorf("Lookup(%q): got %#v, want %#v", sfo, got, afo)
}
if got := Lookup([]byte(sfO)); got != afO {
t.Errorf("Lookup(%q): got %#v, want %#v", sfO, got, afO)
}
if got := afo.String(); got != sfo {
t.Errorf("Atom(%#v).String(): got %q, want %q", afo, got, sfo)
}
if got := afO.String(); got != sfO {
t.Errorf("Atom(%#v).String(): got %q, want %q", afO, got, sfO)
}
}
func BenchmarkLookup(b *testing.B) {
sortedTable := make([]string, 0, len(table))
for _, a := range table {
if a != 0 {
sortedTable = append(sortedTable, a.String())
}
}
sort.Strings(sortedTable)
x := make([][]byte, 1000)
for i := range x {
x[i] = []byte(sortedTable[i%len(sortedTable)])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, s := range x {
Lookup(s)
}
}
}

View file

@ -306,7 +306,7 @@ func (t *table) push(i uint32, depth int) bool {
// The lists of element names and attribute keys were taken from
// https://html.spec.whatwg.org/multipage/indices.html#index
// as of the "HTML Living Standard - Last Updated 18 September 2017" version.
// as of the "HTML Living Standard - Last Updated 16 April 2018" version.
// "command", "keygen" and "menuitem" have been removed from the spec,
// but are kept here for backwards compatibility.
@ -665,6 +665,7 @@ var eventHandlers = []string{
// extra are ad-hoc values not covered by any of the lists above.
var extra = []string{
"acronym",
"align",
"annotation",
"annotation-xml",
@ -700,6 +701,8 @@ var extra = []string{
"plaintext",
"prompt",
"public",
"rb",
"rtc",
"spacer",
"strike",
"svg",

File diff suppressed because it is too large Load diff

View file

@ -1,373 +0,0 @@
// Code generated by go generate gen.go; DO NOT EDIT.
//go:generate go run gen.go -test
package atom
var testAtomList = []string{
"a",
"abbr",
"accept",
"accept-charset",
"accesskey",
"action",
"address",
"align",
"allowfullscreen",
"allowpaymentrequest",
"allowusermedia",
"alt",
"annotation",
"annotation-xml",
"applet",
"area",
"article",
"as",
"aside",
"async",
"audio",
"autocomplete",
"autofocus",
"autoplay",
"b",
"base",
"basefont",
"bdi",
"bdo",
"bgsound",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"challenge",
"charset",
"checked",
"cite",
"class",
"code",
"col",
"colgroup",
"color",
"cols",
"colspan",
"command",
"content",
"contenteditable",
"contextmenu",
"controls",
"coords",
"crossorigin",
"data",
"datalist",
"datetime",
"dd",
"default",
"defer",
"del",
"desc",
"details",
"dfn",
"dialog",
"dir",
"dirname",
"disabled",
"div",
"dl",
"download",
"draggable",
"dropzone",
"dt",
"em",
"embed",
"enctype",
"face",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"for",
"foreignObject",
"foreignobject",
"form",
"formaction",
"formenctype",
"formmethod",
"formnovalidate",
"formtarget",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"headers",
"height",
"hgroup",
"hidden",
"high",
"hr",
"href",
"hreflang",
"html",
"http-equiv",
"i",
"icon",
"id",
"iframe",
"image",
"img",
"input",
"inputmode",
"ins",
"integrity",
"is",
"isindex",
"ismap",
"itemid",
"itemprop",
"itemref",
"itemscope",
"itemtype",
"kbd",
"keygen",
"keytype",
"kind",
"label",
"lang",
"legend",
"li",
"link",
"list",
"listing",
"loop",
"low",
"main",
"malignmark",
"manifest",
"map",
"mark",
"marquee",
"math",
"max",
"maxlength",
"media",
"mediagroup",
"menu",
"menuitem",
"meta",
"meter",
"method",
"mglyph",
"mi",
"min",
"minlength",
"mn",
"mo",
"ms",
"mtext",
"multiple",
"muted",
"name",
"nav",
"nobr",
"noembed",
"noframes",
"nomodule",
"nonce",
"noscript",
"novalidate",
"object",
"ol",
"onabort",
"onafterprint",
"onautocomplete",
"onautocompleteerror",
"onauxclick",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncancel",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"onclose",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragexit",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onlanguagechange",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadend",
"onloadstart",
"onmessage",
"onmessageerror",
"onmousedown",
"onmouseenter",
"onmouseleave",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"onpagehide",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"onpopstate",
"onprogress",
"onratechange",
"onrejectionhandled",
"onreset",
"onresize",
"onscroll",
"onsecuritypolicyviolation",
"onseeked",
"onseeking",
"onselect",
"onshow",
"onsort",
"onstalled",
"onstorage",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunhandledrejection",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optgroup",
"optimum",
"option",
"output",
"p",
"param",
"pattern",
"picture",
"ping",
"placeholder",
"plaintext",
"playsinline",
"poster",
"pre",
"preload",
"progress",
"prompt",
"public",
"q",
"radiogroup",
"readonly",
"referrerpolicy",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"rp",
"rt",
"ruby",
"s",
"samp",
"sandbox",
"scope",
"scoped",
"script",
"seamless",
"section",
"select",
"selected",
"shape",
"size",
"sizes",
"slot",
"small",
"sortable",
"sorted",
"source",
"spacer",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"system",
"tabindex",
"table",
"target",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"translate",
"tt",
"type",
"typemustmatch",
"u",
"ul",
"updateviacache",
"usemap",
"value",
"var",
"video",
"wbr",
"width",
"workertype",
"wrap",
"xmp",
}

View file

@ -1,237 +0,0 @@
// Copyright 2013 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 charset
import (
"bytes"
"encoding/xml"
"io/ioutil"
"runtime"
"strings"
"testing"
"golang.org/x/text/transform"
)
func transformString(t transform.Transformer, s string) (string, error) {
r := transform.NewReader(strings.NewReader(s), t)
b, err := ioutil.ReadAll(r)
return string(b), err
}
type testCase struct {
utf8, other, otherEncoding string
}
// testCases for encoding and decoding.
var testCases = []testCase{
{"Résumé", "Résumé", "utf8"},
{"Résumé", "R\xe9sum\xe9", "latin1"},
{"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"},
{"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"},
{"Hello, world", "Hello, world", "ASCII"},
{"Gdańsk", "Gda\xf1sk", "ISO-8859-2"},
{"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"},
{"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"},
{"latviešu", "latvie\xf0u", "ISO-8859-13"},
{"Seònaid", "Se\xf2naid", "ISO-8859-14"},
{"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"},
{"românește", "rom\xe2ne\xbate", "ISO-8859-16"},
{"nutraĵo", "nutra\xbco", "ISO-8859-3"},
{"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"},
{"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"},
{"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"},
{"Kağan", "Ka\xf0an", "ISO-8859-9"},
{"Résumé", "R\x8esum\x8e", "macintosh"},
{"Gdańsk", "Gda\xf1sk", "windows-1250"},
{"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"},
{"Résumé", "R\xe9sum\xe9", "windows-1252"},
{"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"},
{"Kağan", "Ka\xf0an", "windows-1254"},
{"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"},
{"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"},
{"latviešu", "latvie\xf0u", "windows-1257"},
{"Việt", "Vi\xea\xf2t", "windows-1258"},
{"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"},
{"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"},
{"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"},
{"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"},
{"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"},
{"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"},
{"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"},
{"㧯", "\x82\x31\x89\x38", "gb18030"},
{"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"},
{"Hello, 世界!", "Hello, \x90\xa2\x8aE!", "SJIS"},
{"イウエオカ", "\xb2\xb3\xb4\xb5\xb6", "SJIS"},
{"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"},
{"Hello, 世界!", "Hello, \x1b$B@$3&\x1b(B!", "ISO-2022-JP"},
{"다음과 같은 조건을 따라야 합니다: 저작자표시", "\xb4\xd9\xc0\xbd\xb0\xfa \xb0\xb0\xc0\xba \xc1\xb6\xb0\xc7\xc0\xbb \xb5\xfb\xb6\xf3\xbe\xdf \xc7մϴ\xd9: \xc0\xfa\xc0\xdb\xc0\xdaǥ\xbd\xc3", "EUC-KR"},
}
func TestDecode(t *testing.T) {
testCases := append(testCases, []testCase{
// Replace multi-byte maximum subpart of ill-formed subsequence with
// single replacement character (WhatWG requirement).
{"Rés\ufffdumé", "Rés\xe1\x80umé", "utf8"},
}...)
for _, tc := range testCases {
e, _ := Lookup(tc.otherEncoding)
if e == nil {
t.Errorf("%s: not found", tc.otherEncoding)
continue
}
s, err := transformString(e.NewDecoder(), tc.other)
if err != nil {
t.Errorf("%s: decode %q: %v", tc.otherEncoding, tc.other, err)
continue
}
if s != tc.utf8 {
t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.utf8)
}
}
}
func TestEncode(t *testing.T) {
testCases := append(testCases, []testCase{
// Use Go-style replacement.
{"Rés\xe1\x80umé", "Rés\ufffd\ufffdumé", "utf8"},
// U+0144 LATIN SMALL LETTER N WITH ACUTE not supported by encoding.
{"Gdańsk", "Gda&#324;sk", "ISO-8859-11"},
{"\ufffd", "&#65533;", "ISO-8859-11"},
{"a\xe1\x80b", "a&#65533;&#65533;b", "ISO-8859-11"},
}...)
for _, tc := range testCases {
e, _ := Lookup(tc.otherEncoding)
if e == nil {
t.Errorf("%s: not found", tc.otherEncoding)
continue
}
s, err := transformString(e.NewEncoder(), tc.utf8)
if err != nil {
t.Errorf("%s: encode %q: %s", tc.otherEncoding, tc.utf8, err)
continue
}
if s != tc.other {
t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.other)
}
}
}
var sniffTestCases = []struct {
filename, declared, want string
}{
{"HTTP-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
{"UTF-16LE-BOM.html", "", "utf-16le"},
{"UTF-16BE-BOM.html", "", "utf-16be"},
{"meta-content-attribute.html", "text/html", "iso-8859-15"},
{"meta-charset-attribute.html", "text/html", "iso-8859-15"},
{"No-encoding-declaration.html", "text/html", "utf-8"},
{"HTTP-vs-UTF-8-BOM.html", "text/html; charset=iso-8859-15", "utf-8"},
{"HTTP-vs-meta-content.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
{"HTTP-vs-meta-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
{"UTF-8-BOM-vs-meta-content.html", "text/html", "utf-8"},
{"UTF-8-BOM-vs-meta-charset.html", "text/html", "utf-8"},
}
func TestSniff(t *testing.T) {
switch runtime.GOOS {
case "nacl": // platforms that don't permit direct file system access
t.Skipf("not supported on %q", runtime.GOOS)
}
for _, tc := range sniffTestCases {
content, err := ioutil.ReadFile("testdata/" + tc.filename)
if err != nil {
t.Errorf("%s: error reading file: %v", tc.filename, err)
continue
}
_, name, _ := DetermineEncoding(content, tc.declared)
if name != tc.want {
t.Errorf("%s: got %q, want %q", tc.filename, name, tc.want)
continue
}
}
}
func TestReader(t *testing.T) {
switch runtime.GOOS {
case "nacl": // platforms that don't permit direct file system access
t.Skipf("not supported on %q", runtime.GOOS)
}
for _, tc := range sniffTestCases {
content, err := ioutil.ReadFile("testdata/" + tc.filename)
if err != nil {
t.Errorf("%s: error reading file: %v", tc.filename, err)
continue
}
r, err := NewReader(bytes.NewReader(content), tc.declared)
if err != nil {
t.Errorf("%s: error creating reader: %v", tc.filename, err)
continue
}
got, err := ioutil.ReadAll(r)
if err != nil {
t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err)
continue
}
e, _ := Lookup(tc.want)
want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder()))
if err != nil {
t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err)
continue
}
if !bytes.Equal(got, want) {
t.Errorf("%s: got %q, want %q", tc.filename, got, want)
continue
}
}
}
var metaTestCases = []struct {
meta, want string
}{
{"", ""},
{"text/html", ""},
{"text/html; charset utf-8", ""},
{"text/html; charset=latin-2", "latin-2"},
{"text/html; charset; charset = utf-8", "utf-8"},
{`charset="big5"`, "big5"},
{"charset='shift_jis'", "shift_jis"},
}
func TestFromMeta(t *testing.T) {
for _, tc := range metaTestCases {
got := fromMetaElement(tc.meta)
if got != tc.want {
t.Errorf("%q: got %q, want %q", tc.meta, got, tc.want)
}
}
}
func TestXML(t *testing.T) {
const s = "<?xml version=\"1.0\" encoding=\"windows-1252\"?><a><Word>r\xe9sum\xe9</Word></a>"
d := xml.NewDecoder(strings.NewReader(s))
d.CharsetReader = NewReaderLabel
var a struct {
Word string
}
err := d.Decode(&a)
if err != nil {
t.Fatalf("Decode: %v", err)
}
want := "résumé"
if a.Word != want {
t.Errorf("got %q, want %q", a.Word, want)
}
}

View file

@ -4,7 +4,7 @@
package html
// Section 12.2.3.2 of the HTML5 specification says "The following elements
// Section 12.2.4.2 of the HTML5 specification says "The following elements
// have varying levels of special parsing rules".
// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
var isSpecialElementMap = map[string]bool{

View file

@ -49,18 +49,18 @@ call to Next. For example, to extract an HTML page's anchor text:
for {
tt := z.Next()
switch tt {
case ErrorToken:
case html.ErrorToken:
return z.Err()
case TextToken:
case html.TextToken:
if depth > 0 {
// emitBytes should copy the []byte it receives,
// if it doesn't process it immediately.
emitBytes(z.Text())
}
case StartTagToken, EndTagToken:
case html.StartTagToken, html.EndTagToken:
tn, _ := z.TagName()
if len(tn) == 1 && tn[0] == 'a' {
if tt == StartTagToken {
if tt == html.StartTagToken {
depth++
} else {
depth--

4154
vendor/golang.org/x/net/html/entity.go generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,29 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import (
"testing"
"unicode/utf8"
)
func TestEntityLength(t *testing.T) {
// We verify that the length of UTF-8 encoding of each value is <= 1 + len(key).
// The +1 comes from the leading "&". This property implies that the length of
// unescaped text is <= the length of escaped text.
for k, v := range entity {
if 1+len(k) < utf8.RuneLen(v) {
t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v))
}
if len(k) > longestEntityWithoutSemicolon && k[len(k)-1] != ';' {
t.Errorf("entity name %s is %d characters, but longestEntityWithoutSemicolon=%d", k, len(k), longestEntityWithoutSemicolon)
}
}
for k, v := range entity2 {
if 1+len(k) < utf8.RuneLen(v[0])+utf8.RuneLen(v[1]) {
t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v[0]) + string(v[1]))
}
}
}

View file

@ -1,97 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import "testing"
type unescapeTest struct {
// A short description of the test case.
desc string
// The HTML text.
html string
// The unescaped text.
unescaped string
}
var unescapeTests = []unescapeTest{
// Handle no entities.
{
"copy",
"A\ttext\nstring",
"A\ttext\nstring",
},
// Handle simple named entities.
{
"simple",
"&amp; &gt; &lt;",
"& > <",
},
// Handle hitting the end of the string.
{
"stringEnd",
"&amp &amp",
"& &",
},
// Handle entities with two codepoints.
{
"multiCodepoint",
"text &gesl; blah",
"text \u22db\ufe00 blah",
},
// Handle decimal numeric entities.
{
"decimalEntity",
"Delta = &#916; ",
"Delta = Δ ",
},
// Handle hexadecimal numeric entities.
{
"hexadecimalEntity",
"Lambda = &#x3bb; = &#X3Bb ",
"Lambda = λ = λ ",
},
// Handle numeric early termination.
{
"numericEnds",
"&# &#x &#128;43 &copy = &#169f = &#xa9",
"&# &#x €43 © = ©f = ©",
},
// Handle numeric ISO-8859-1 entity replacements.
{
"numericReplacements",
"Footnote&#x87;",
"Footnote‡",
},
}
func TestUnescape(t *testing.T) {
for _, tt := range unescapeTests {
unescaped := UnescapeString(tt.html)
if unescaped != tt.unescaped {
t.Errorf("TestUnescape %s: want %q, got %q", tt.desc, tt.unescaped, unescaped)
}
}
}
func TestUnescapeEscape(t *testing.T) {
ss := []string{
``,
`abc def`,
`a & b`,
`a&amp;b`,
`a &amp b`,
`&quot;`,
`"`,
`"<&>"`,
`&quot;&lt;&amp;&gt;&quot;`,
`3&5==1 && 0<1, "0&lt;1", a+acute=&aacute;`,
`The special characters are: <, >, &, ' and "`,
}
for _, s := range ss {
if got := UnescapeString(EscapeString(s)); got != s {
t.Errorf("got %q want %q", got, s)
}
}
}

View file

@ -1,40 +0,0 @@
// Copyright 2012 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.
// This example demonstrates parsing HTML data and walking the resulting tree.
package html_test
import (
"fmt"
"log"
"strings"
"golang.org/x/net/html"
)
func ExampleParse() {
s := `<p>Links:</p><ul><li><a href="foo">Foo</a><li><a href="/bar/baz">BarBaz</a></ul>`
doc, err := html.Parse(strings.NewReader(s))
if err != nil {
log.Fatal(err)
}
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
fmt.Println(a.Val)
break
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
// Output:
// foo
// /bar/baz
}

View file

@ -67,7 +67,7 @@ func mathMLTextIntegrationPoint(n *Node) bool {
return false
}
// Section 12.2.5.5.
// Section 12.2.6.5.
var breakout = map[string]bool{
"b": true,
"big": true,
@ -115,7 +115,7 @@ var breakout = map[string]bool{
"var": true,
}
// Section 12.2.5.5.
// Section 12.2.6.5.
var svgTagNameAdjustments = map[string]string{
"altglyph": "altGlyph",
"altglyphdef": "altGlyphDef",
@ -155,7 +155,7 @@ var svgTagNameAdjustments = map[string]string{
"textpath": "textPath",
}
// Section 12.2.5.1
// Section 12.2.6.1
var mathMLAttributeAdjustments = map[string]string{
"definitionurl": "definitionURL",
}

33
vendor/golang.org/x/net/html/node.go generated vendored
View file

@ -21,9 +21,10 @@ const (
scopeMarkerNode
)
// Section 12.2.3.3 says "scope markers are inserted when entering applet
// elements, buttons, object elements, marquees, table cells, and table
// captions, and are used to prevent formatting from 'leaking'".
// Section 12.2.4.3 says "The markers are inserted when entering applet,
// object, marquee, template, td, th, and caption elements, and are used
// to prevent formatting from "leaking" into applet, object, marquee,
// template, td, th, and caption elements".
var scopeMarker = Node{Type: scopeMarkerNode}
// A Node consists of a NodeType and some Data (tag name for element nodes,
@ -173,6 +174,16 @@ func (s *nodeStack) index(n *Node) int {
return -1
}
// contains returns whether a is within s.
func (s *nodeStack) contains(a atom.Atom) bool {
for _, n := range *s {
if n.DataAtom == a {
return true
}
}
return false
}
// insert inserts a node at the given index.
func (s *nodeStack) insert(i int, n *Node) {
(*s) = append(*s, nil)
@ -191,3 +202,19 @@ func (s *nodeStack) remove(n *Node) {
(*s)[j] = nil
*s = (*s)[:j]
}
type insertionModeStack []insertionMode
func (s *insertionModeStack) pop() (im insertionMode) {
i := len(*s)
im = (*s)[i-1]
*s = (*s)[:i-1]
return im
}
func (s *insertionModeStack) top() insertionMode {
if i := len(*s); i > 0 {
return (*s)[i-1]
}
return nil
}

View file

@ -1,146 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import (
"fmt"
)
// checkTreeConsistency checks that a node and its descendants are all
// consistent in their parent/child/sibling relationships.
func checkTreeConsistency(n *Node) error {
return checkTreeConsistency1(n, 0)
}
func checkTreeConsistency1(n *Node, depth int) error {
if depth == 1e4 {
return fmt.Errorf("html: tree looks like it contains a cycle")
}
if err := checkNodeConsistency(n); err != nil {
return err
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if err := checkTreeConsistency1(c, depth+1); err != nil {
return err
}
}
return nil
}
// checkNodeConsistency checks that a node's parent/child/sibling relationships
// are consistent.
func checkNodeConsistency(n *Node) error {
if n == nil {
return nil
}
nParent := 0
for p := n.Parent; p != nil; p = p.Parent {
nParent++
if nParent == 1e4 {
return fmt.Errorf("html: parent list looks like an infinite loop")
}
}
nForward := 0
for c := n.FirstChild; c != nil; c = c.NextSibling {
nForward++
if nForward == 1e6 {
return fmt.Errorf("html: forward list of children looks like an infinite loop")
}
if c.Parent != n {
return fmt.Errorf("html: inconsistent child/parent relationship")
}
}
nBackward := 0
for c := n.LastChild; c != nil; c = c.PrevSibling {
nBackward++
if nBackward == 1e6 {
return fmt.Errorf("html: backward list of children looks like an infinite loop")
}
if c.Parent != n {
return fmt.Errorf("html: inconsistent child/parent relationship")
}
}
if n.Parent != nil {
if n.Parent == n {
return fmt.Errorf("html: inconsistent parent relationship")
}
if n.Parent == n.FirstChild {
return fmt.Errorf("html: inconsistent parent/first relationship")
}
if n.Parent == n.LastChild {
return fmt.Errorf("html: inconsistent parent/last relationship")
}
if n.Parent == n.PrevSibling {
return fmt.Errorf("html: inconsistent parent/prev relationship")
}
if n.Parent == n.NextSibling {
return fmt.Errorf("html: inconsistent parent/next relationship")
}
parentHasNAsAChild := false
for c := n.Parent.FirstChild; c != nil; c = c.NextSibling {
if c == n {
parentHasNAsAChild = true
break
}
}
if !parentHasNAsAChild {
return fmt.Errorf("html: inconsistent parent/child relationship")
}
}
if n.PrevSibling != nil && n.PrevSibling.NextSibling != n {
return fmt.Errorf("html: inconsistent prev/next relationship")
}
if n.NextSibling != nil && n.NextSibling.PrevSibling != n {
return fmt.Errorf("html: inconsistent next/prev relationship")
}
if (n.FirstChild == nil) != (n.LastChild == nil) {
return fmt.Errorf("html: inconsistent first/last relationship")
}
if n.FirstChild != nil && n.FirstChild == n.LastChild {
// We have a sole child.
if n.FirstChild.PrevSibling != nil || n.FirstChild.NextSibling != nil {
return fmt.Errorf("html: inconsistent sole child's sibling relationship")
}
}
seen := map[*Node]bool{}
var last *Node
for c := n.FirstChild; c != nil; c = c.NextSibling {
if seen[c] {
return fmt.Errorf("html: inconsistent repeated child")
}
seen[c] = true
last = c
}
if last != n.LastChild {
return fmt.Errorf("html: inconsistent last relationship")
}
var first *Node
for c := n.LastChild; c != nil; c = c.PrevSibling {
if !seen[c] {
return fmt.Errorf("html: inconsistent missing child")
}
delete(seen, c)
first = c
}
if first != n.FirstChild {
return fmt.Errorf("html: inconsistent first relationship")
}
if len(seen) != 0 {
return fmt.Errorf("html: inconsistent forwards/backwards child list")
}
return nil
}

361
vendor/golang.org/x/net/html/parse.go generated vendored
View file

@ -25,20 +25,22 @@ type parser struct {
hasSelfClosingToken bool
// doc is the document root element.
doc *Node
// The stack of open elements (section 12.2.3.2) and active formatting
// elements (section 12.2.3.3).
// The stack of open elements (section 12.2.4.2) and active formatting
// elements (section 12.2.4.3).
oe, afe nodeStack
// Element pointers (section 12.2.3.4).
// Element pointers (section 12.2.4.4).
head, form *Node
// Other parsing state flags (section 12.2.3.5).
// Other parsing state flags (section 12.2.4.5).
scripting, framesetOK bool
// The stack of template insertion modes
templateStack insertionModeStack
// im is the current insertion mode.
im insertionMode
// originalIM is the insertion mode to go back to after completing a text
// or inTableText insertion mode.
originalIM insertionMode
// fosterParenting is whether new elements should be inserted according to
// the foster parenting rules (section 12.2.5.3).
// the foster parenting rules (section 12.2.6.1).
fosterParenting bool
// quirks is whether the parser is operating in "quirks mode."
quirks bool
@ -56,7 +58,7 @@ func (p *parser) top() *Node {
return p.doc
}
// Stop tags for use in popUntil. These come from section 12.2.3.2.
// Stop tags for use in popUntil. These come from section 12.2.4.2.
var (
defaultScopeStopTags = map[string][]a.Atom{
"": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template},
@ -79,7 +81,7 @@ const (
// popUntil pops the stack of open elements at the highest element whose tag
// is in matchTags, provided there is no higher element in the scope's stop
// tags (as defined in section 12.2.3.2). It returns whether or not there was
// tags (as defined in section 12.2.4.2). It returns whether or not there was
// such an element. If there was not, popUntil leaves the stack unchanged.
//
// For example, the set of stop tags for table scope is: "html", "table". If
@ -126,7 +128,7 @@ func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
return -1
}
case tableScope:
if tagAtom == a.Html || tagAtom == a.Table {
if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template {
return -1
}
case selectScope:
@ -162,17 +164,17 @@ func (p *parser) clearStackToContext(s scope) {
tagAtom := p.oe[i].DataAtom
switch s {
case tableScope:
if tagAtom == a.Html || tagAtom == a.Table {
if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template {
p.oe = p.oe[:i+1]
return
}
case tableRowScope:
if tagAtom == a.Html || tagAtom == a.Tr {
if tagAtom == a.Html || tagAtom == a.Tr || tagAtom == a.Template {
p.oe = p.oe[:i+1]
return
}
case tableBodyScope:
if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead {
if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead || tagAtom == a.Template {
p.oe = p.oe[:i+1]
return
}
@ -183,7 +185,7 @@ func (p *parser) clearStackToContext(s scope) {
}
// generateImpliedEndTags pops nodes off the stack of open elements as long as
// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt.
// the top node has a tag name of dd, dt, li, optgroup, option, p, rb, rp, rt or rtc.
// If exceptions are specified, nodes with that name will not be popped off.
func (p *parser) generateImpliedEndTags(exceptions ...string) {
var i int
@ -192,7 +194,7 @@ loop:
n := p.oe[i]
if n.Type == ElementNode {
switch n.DataAtom {
case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt:
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc:
for _, except := range exceptions {
if n.Data == except {
break loop
@ -207,6 +209,27 @@ loop:
p.oe = p.oe[:i+1]
}
// generateAllImpliedEndTags pops nodes off the stack of open elements as long as
// the top node has a tag name of caption, colgroup, dd, div, dt, li, optgroup, option, p, rb,
// rp, rt, rtc, span, tbody, td, tfoot, th, thead or tr.
func (p *parser) generateAllImpliedEndTags() {
var i int
for i = len(p.oe) - 1; i >= 0; i-- {
n := p.oe[i]
if n.Type == ElementNode {
switch n.DataAtom {
// TODO: remove this divergence from the HTML5 spec
case a.Caption, a.Colgroup, a.Dd, a.Div, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb,
a.Rp, a.Rt, a.Rtc, a.Span, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
continue
}
}
break
}
p.oe = p.oe[:i+1]
}
// addChild adds a child node n to the top element, and pushes n onto the stack
// of open elements if it is an element node.
func (p *parser) addChild(n *Node) {
@ -234,9 +257,9 @@ func (p *parser) shouldFosterParent() bool {
}
// fosterParent adds a child node according to the foster parenting rules.
// Section 12.2.5.3, "foster parenting".
// Section 12.2.6.1, "foster parenting".
func (p *parser) fosterParent(n *Node) {
var table, parent, prev *Node
var table, parent, prev, template *Node
var i int
for i = len(p.oe) - 1; i >= 0; i-- {
if p.oe[i].DataAtom == a.Table {
@ -245,6 +268,19 @@ func (p *parser) fosterParent(n *Node) {
}
}
var j int
for j = len(p.oe) - 1; j >= 0; j-- {
if p.oe[j].DataAtom == a.Template {
template = p.oe[j]
break
}
}
if template != nil && (table == nil || j < i) {
template.AppendChild(n)
return
}
if table == nil {
// The foster parent is the html element.
parent = p.oe[0]
@ -304,7 +340,7 @@ func (p *parser) addElement() {
})
}
// Section 12.2.3.3.
// Section 12.2.4.3.
func (p *parser) addFormattingElement() {
tagAtom, attr := p.tok.DataAtom, p.tok.Attr
p.addElement()
@ -351,7 +387,7 @@ findIdenticalElements:
p.afe = append(p.afe, p.top())
}
// Section 12.2.3.3.
// Section 12.2.4.3.
func (p *parser) clearActiveFormattingElements() {
for {
n := p.afe.pop()
@ -361,7 +397,7 @@ func (p *parser) clearActiveFormattingElements() {
}
}
// Section 12.2.3.3.
// Section 12.2.4.3.
func (p *parser) reconstructActiveFormattingElements() {
n := p.afe.top()
if n == nil {
@ -390,12 +426,12 @@ func (p *parser) reconstructActiveFormattingElements() {
}
}
// Section 12.2.4.
// Section 12.2.5.
func (p *parser) acknowledgeSelfClosingTag() {
p.hasSelfClosingToken = false
}
// An insertion mode (section 12.2.3.1) is the state transition function from
// An insertion mode (section 12.2.4.1) is the state transition function from
// a particular state in the HTML5 parser's state machine. It updates the
// parser's fields depending on parser.tok (where ErrorToken means EOF).
// It returns whether the token was consumed.
@ -403,7 +439,7 @@ type insertionMode func(*parser) bool
// setOriginalIM sets the insertion mode to return to after completing a text or
// inTableText insertion mode.
// Section 12.2.3.1, "using the rules for".
// Section 12.2.4.1, "using the rules for".
func (p *parser) setOriginalIM() {
if p.originalIM != nil {
panic("html: bad parser state: originalIM was set twice")
@ -411,18 +447,38 @@ func (p *parser) setOriginalIM() {
p.originalIM = p.im
}
// Section 12.2.3.1, "reset the insertion mode".
// Section 12.2.4.1, "reset the insertion mode".
func (p *parser) resetInsertionMode() {
for i := len(p.oe) - 1; i >= 0; i-- {
n := p.oe[i]
if i == 0 && p.context != nil {
last := i == 0
if last && p.context != nil {
n = p.context
}
switch n.DataAtom {
case a.Select:
if !last {
for ancestor, first := n, p.oe[0]; ancestor != first; {
if ancestor == first {
break
}
ancestor = p.oe[p.oe.index(ancestor)-1]
switch ancestor.DataAtom {
case a.Template:
p.im = inSelectIM
return
case a.Table:
p.im = inSelectInTableIM
return
}
}
}
p.im = inSelectIM
case a.Td, a.Th:
// TODO: remove this divergence from the HTML5 spec.
//
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
p.im = inCellIM
case a.Tr:
p.im = inRowIM
@ -434,25 +490,37 @@ func (p *parser) resetInsertionMode() {
p.im = inColumnGroupIM
case a.Table:
p.im = inTableIM
case a.Template:
p.im = p.templateStack.top()
case a.Head:
p.im = inBodyIM
// TODO: remove this divergence from the HTML5 spec.
//
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
p.im = inHeadIM
case a.Body:
p.im = inBodyIM
case a.Frameset:
p.im = inFramesetIM
case a.Html:
p.im = beforeHeadIM
if p.head == nil {
p.im = beforeHeadIM
} else {
p.im = afterHeadIM
}
default:
if last {
p.im = inBodyIM
return
}
continue
}
return
}
p.im = inBodyIM
}
const whitespace = " \t\r\n\f"
// Section 12.2.5.4.1.
// Section 12.2.6.4.1.
func initialIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -479,7 +547,7 @@ func initialIM(p *parser) bool {
return false
}
// Section 12.2.5.4.2.
// Section 12.2.6.4.2.
func beforeHTMLIM(p *parser) bool {
switch p.tok.Type {
case DoctypeToken:
@ -517,7 +585,7 @@ func beforeHTMLIM(p *parser) bool {
return false
}
// Section 12.2.5.4.3.
// Section 12.2.6.4.3.
func beforeHeadIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -560,7 +628,7 @@ func beforeHeadIM(p *parser) bool {
return false
}
// Section 12.2.5.4.4.
// Section 12.2.6.4.4.
func inHeadIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -590,19 +658,36 @@ func inHeadIM(p *parser) bool {
case a.Head:
// Ignore the token.
return true
case a.Template:
p.addElement()
p.afe = append(p.afe, &scopeMarker)
p.framesetOK = false
p.im = inTemplateIM
p.templateStack = append(p.templateStack, inTemplateIM)
return true
}
case EndTagToken:
switch p.tok.DataAtom {
case a.Head:
n := p.oe.pop()
if n.DataAtom != a.Head {
panic("html: bad parser state: <head> element not found, in the in-head insertion mode")
}
p.oe.pop()
p.im = afterHeadIM
return true
case a.Body, a.Html, a.Br:
p.parseImpliedToken(EndTagToken, a.Head, a.Head.String())
return false
case a.Template:
if !p.oe.contains(a.Template) {
return true
}
p.generateAllImpliedEndTags()
if n := p.oe.top(); n.DataAtom != a.Template {
return true
}
p.popUntil(defaultScope, a.Template)
p.clearActiveFormattingElements()
p.templateStack.pop()
p.resetInsertionMode()
return true
default:
// Ignore the token.
return true
@ -622,7 +707,7 @@ func inHeadIM(p *parser) bool {
return false
}
// Section 12.2.5.4.6.
// Section 12.2.6.4.6.
func afterHeadIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -648,7 +733,7 @@ func afterHeadIM(p *parser) bool {
p.addElement()
p.im = inFramesetIM
return true
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
p.oe = append(p.oe, p.head)
defer p.oe.remove(p.head)
return inHeadIM(p)
@ -660,6 +745,8 @@ func afterHeadIM(p *parser) bool {
switch p.tok.DataAtom {
case a.Body, a.Html, a.Br:
// Drop down to creating an implied <body> tag.
case a.Template:
return inHeadIM(p)
default:
// Ignore the token.
return true
@ -697,7 +784,7 @@ func copyAttributes(dst *Node, src Token) {
}
}
// Section 12.2.5.4.7.
// Section 12.2.6.4.7.
func inBodyIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -727,10 +814,16 @@ func inBodyIM(p *parser) bool {
case StartTagToken:
switch p.tok.DataAtom {
case a.Html:
if p.oe.contains(a.Template) {
return true
}
copyAttributes(p.oe[0], p.tok)
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
return inHeadIM(p)
case a.Body:
if p.oe.contains(a.Template) {
return true
}
if len(p.oe) >= 2 {
body := p.oe[1]
if body.Type == ElementNode && body.DataAtom == a.Body {
@ -767,7 +860,7 @@ func inBodyIM(p *parser) bool {
// The newline, if any, will be dealt with by the TextToken case.
p.framesetOK = false
case a.Form:
if p.form == nil {
if p.oe.contains(a.Template) || p.form == nil {
p.popUntil(buttonScope, a.P)
p.addElement()
p.form = p.top()
@ -952,11 +1045,16 @@ func inBodyIM(p *parser) bool {
}
p.reconstructActiveFormattingElements()
p.addElement()
case a.Rp, a.Rt:
case a.Rb, a.Rtc:
if p.elementInScope(defaultScope, a.Ruby) {
p.generateImpliedEndTags()
}
p.addElement()
case a.Rp, a.Rt:
if p.elementInScope(defaultScope, a.Ruby) {
p.generateImpliedEndTags("rtc")
}
p.addElement()
case a.Math, a.Svg:
p.reconstructActiveFormattingElements()
if p.tok.DataAtom == a.Math {
@ -972,7 +1070,13 @@ func inBodyIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
}
return true
case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
case a.Frame:
// TODO: remove this divergence from the HTML5 spec.
if p.oe.contains(a.Template) {
p.addElement()
return true
}
case a.Caption, a.Col, a.Colgroup, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
// Ignore the token.
default:
p.reconstructActiveFormattingElements()
@ -993,15 +1097,28 @@ func inBodyIM(p *parser) bool {
case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
p.popUntil(defaultScope, p.tok.DataAtom)
case a.Form:
node := p.form
p.form = nil
i := p.indexOfElementInScope(defaultScope, a.Form)
if node == nil || i == -1 || p.oe[i] != node {
// Ignore the token.
return true
if p.oe.contains(a.Template) {
if !p.oe.contains(a.Form) {
// Ignore the token.
return true
}
p.generateImpliedEndTags()
if p.tok.DataAtom == a.Form {
// Ignore the token.
return true
}
p.popUntil(defaultScope, a.Form)
} else {
node := p.form
p.form = nil
i := p.indexOfElementInScope(defaultScope, a.Form)
if node == nil || i == -1 || p.oe[i] != node {
// Ignore the token.
return true
}
p.generateImpliedEndTags()
p.oe.remove(node)
}
p.generateImpliedEndTags()
p.oe.remove(node)
case a.P:
if !p.elementInScope(buttonScope, a.P) {
p.parseImpliedToken(StartTagToken, a.P, a.P.String())
@ -1022,6 +1139,8 @@ func inBodyIM(p *parser) bool {
case a.Br:
p.tok.Type = StartTagToken
return false
case a.Template:
return inHeadIM(p)
default:
p.inBodyEndTagOther(p.tok.DataAtom)
}
@ -1030,6 +1149,21 @@ func inBodyIM(p *parser) bool {
Type: CommentNode,
Data: p.tok.Data,
})
case ErrorToken:
// TODO: remove this divergence from the HTML5 spec.
if len(p.templateStack) > 0 {
p.im = inTemplateIM
return false
} else {
for _, e := range p.oe {
switch e.DataAtom {
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc, a.Tbody, a.Td, a.Tfoot, a.Th,
a.Thead, a.Tr, a.Body, a.Html:
default:
return true
}
}
}
}
return true
@ -1135,6 +1269,12 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
switch commonAncestor.DataAtom {
case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
p.fosterParent(lastNode)
case a.Template:
// TODO: remove namespace checking
if commonAncestor.Namespace == "html" {
commonAncestor = commonAncestor.LastChild
}
fallthrough
default:
commonAncestor.AppendChild(lastNode)
}
@ -1160,7 +1300,7 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
}
// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
// "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
// "Any other end tag" handling from 12.2.6.5 The rules for parsing tokens in foreign content
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
for i := len(p.oe) - 1; i >= 0; i-- {
@ -1174,7 +1314,7 @@ func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
}
}
// Section 12.2.5.4.8.
// Section 12.2.6.4.8.
func textIM(p *parser) bool {
switch p.tok.Type {
case ErrorToken:
@ -1203,7 +1343,7 @@ func textIM(p *parser) bool {
return p.tok.Type == EndTagToken
}
// Section 12.2.5.4.9.
// Section 12.2.6.4.9.
func inTableIM(p *parser) bool {
switch p.tok.Type {
case ErrorToken:
@ -1249,7 +1389,7 @@ func inTableIM(p *parser) bool {
}
// Ignore the token.
return true
case a.Style, a.Script:
case a.Style, a.Script, a.Template:
return inHeadIM(p)
case a.Input:
for _, t := range p.tok.Attr {
@ -1261,7 +1401,7 @@ func inTableIM(p *parser) bool {
}
// Otherwise drop down to the default action.
case a.Form:
if p.form != nil {
if p.oe.contains(a.Template) || p.form != nil {
// Ignore the token.
return true
}
@ -1291,6 +1431,8 @@ func inTableIM(p *parser) bool {
case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
// Ignore the token.
return true
case a.Template:
return inHeadIM(p)
}
case CommentToken:
p.addChild(&Node{
@ -1309,7 +1451,7 @@ func inTableIM(p *parser) bool {
return inBodyIM(p)
}
// Section 12.2.5.4.11.
// Section 12.2.6.4.11.
func inCaptionIM(p *parser) bool {
switch p.tok.Type {
case StartTagToken:
@ -1355,7 +1497,7 @@ func inCaptionIM(p *parser) bool {
return inBodyIM(p)
}
// Section 12.2.5.4.12.
// Section 12.2.6.4.12.
func inColumnGroupIM(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -1386,11 +1528,13 @@ func inColumnGroupIM(p *parser) bool {
p.oe.pop()
p.acknowledgeSelfClosingTag()
return true
case a.Template:
return inHeadIM(p)
}
case EndTagToken:
switch p.tok.DataAtom {
case a.Colgroup:
if p.oe.top().DataAtom != a.Html {
if p.oe.top().DataAtom == a.Colgroup {
p.oe.pop()
p.im = inTableIM
}
@ -1398,17 +1542,19 @@ func inColumnGroupIM(p *parser) bool {
case a.Col:
// Ignore the token.
return true
case a.Template:
return inHeadIM(p)
}
}
if p.oe.top().DataAtom != a.Html {
p.oe.pop()
p.im = inTableIM
return false
if p.oe.top().DataAtom != a.Colgroup {
return true
}
return true
p.oe.pop()
p.im = inTableIM
return false
}
// Section 12.2.5.4.13.
// Section 12.2.6.4.13.
func inTableBodyIM(p *parser) bool {
switch p.tok.Type {
case StartTagToken:
@ -1460,7 +1606,7 @@ func inTableBodyIM(p *parser) bool {
return inTableIM(p)
}
// Section 12.2.5.4.14.
// Section 12.2.6.4.14.
func inRowIM(p *parser) bool {
switch p.tok.Type {
case StartTagToken:
@ -1511,7 +1657,7 @@ func inRowIM(p *parser) bool {
return inTableIM(p)
}
// Section 12.2.5.4.15.
// Section 12.2.6.4.15.
func inCellIM(p *parser) bool {
switch p.tok.Type {
case StartTagToken:
@ -1560,7 +1706,7 @@ func inCellIM(p *parser) bool {
return inBodyIM(p)
}
// Section 12.2.5.4.16.
// Section 12.2.6.4.16.
func inSelectIM(p *parser) bool {
switch p.tok.Type {
case ErrorToken:
@ -1597,7 +1743,7 @@ func inSelectIM(p *parser) bool {
p.tokenizer.NextIsNotRawText()
// Ignore the token.
return true
case a.Script:
case a.Script, a.Template:
return inHeadIM(p)
}
case EndTagToken:
@ -1618,6 +1764,8 @@ func inSelectIM(p *parser) bool {
if p.popUntil(selectScope, a.Select) {
p.resetInsertionMode()
}
case a.Template:
return inHeadIM(p)
}
case CommentToken:
p.addChild(&Node{
@ -1632,7 +1780,7 @@ func inSelectIM(p *parser) bool {
return true
}
// Section 12.2.5.4.17.
// Section 12.2.6.4.17.
func inSelectInTableIM(p *parser) bool {
switch p.tok.Type {
case StartTagToken, EndTagToken:
@ -1650,7 +1798,62 @@ func inSelectInTableIM(p *parser) bool {
return inSelectIM(p)
}
// Section 12.2.5.4.18.
// Section 12.2.6.4.18.
func inTemplateIM(p *parser) bool {
switch p.tok.Type {
case TextToken, CommentToken, DoctypeToken:
return inBodyIM(p)
case StartTagToken:
switch p.tok.DataAtom {
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
return inHeadIM(p)
case a.Caption, a.Colgroup, a.Tbody, a.Tfoot, a.Thead:
p.templateStack.pop()
p.templateStack = append(p.templateStack, inTableIM)
p.im = inTableIM
return false
case a.Col:
p.templateStack.pop()
p.templateStack = append(p.templateStack, inColumnGroupIM)
p.im = inColumnGroupIM
return false
case a.Tr:
p.templateStack.pop()
p.templateStack = append(p.templateStack, inTableBodyIM)
p.im = inTableBodyIM
return false
case a.Td, a.Th:
p.templateStack.pop()
p.templateStack = append(p.templateStack, inRowIM)
p.im = inRowIM
return false
default:
p.templateStack.pop()
p.templateStack = append(p.templateStack, inBodyIM)
p.im = inBodyIM
return false
}
case EndTagToken:
switch p.tok.DataAtom {
case a.Template:
return inHeadIM(p)
default:
// Ignore the token.
return true
}
}
if !p.oe.contains(a.Template) {
// Ignore the token.
return true
}
p.popUntil(defaultScope, a.Template)
p.clearActiveFormattingElements()
p.templateStack.pop()
p.resetInsertionMode()
return false
}
// Section 12.2.6.4.19.
func afterBodyIM(p *parser) bool {
switch p.tok.Type {
case ErrorToken:
@ -1688,7 +1891,7 @@ func afterBodyIM(p *parser) bool {
return false
}
// Section 12.2.5.4.19.
// Section 12.2.6.4.20.
func inFramesetIM(p *parser) bool {
switch p.tok.Type {
case CommentToken:
@ -1720,6 +1923,11 @@ func inFramesetIM(p *parser) bool {
p.acknowledgeSelfClosingTag()
case a.Noframes:
return inHeadIM(p)
case a.Template:
// TODO: remove this divergence from the HTML5 spec.
//
// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668
return inTemplateIM(p)
}
case EndTagToken:
switch p.tok.DataAtom {
@ -1738,7 +1946,7 @@ func inFramesetIM(p *parser) bool {
return true
}
// Section 12.2.5.4.20.
// Section 12.2.6.4.21.
func afterFramesetIM(p *parser) bool {
switch p.tok.Type {
case CommentToken:
@ -1777,7 +1985,7 @@ func afterFramesetIM(p *parser) bool {
return true
}
// Section 12.2.5.4.21.
// Section 12.2.6.4.22.
func afterAfterBodyIM(p *parser) bool {
switch p.tok.Type {
case ErrorToken:
@ -1806,7 +2014,7 @@ func afterAfterBodyIM(p *parser) bool {
return false
}
// Section 12.2.5.4.22.
// Section 12.2.6.4.23.
func afterAfterFramesetIM(p *parser) bool {
switch p.tok.Type {
case CommentToken:
@ -1844,7 +2052,7 @@ func afterAfterFramesetIM(p *parser) bool {
const whitespaceOrNUL = whitespace + "\x00"
// Section 12.2.5.5.
// Section 12.2.6.5
func parseForeignContent(p *parser) bool {
switch p.tok.Type {
case TextToken:
@ -1924,7 +2132,7 @@ func parseForeignContent(p *parser) bool {
return true
}
// Section 12.2.5.
// Section 12.2.6.
func (p *parser) inForeignContent() bool {
if len(p.oe) == 0 {
return false
@ -2064,6 +2272,9 @@ func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
}
p.doc.AppendChild(root)
p.oe = nodeStack{root}
if context != nil && context.DataAtom == a.Template {
p.templateStack = append(p.templateStack, inTemplateIM)
}
p.resetInsertionMode()
for n := context; n != nil; n = n.Parent {

View file

@ -1,388 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
"golang.org/x/net/html/atom"
)
// readParseTest reads a single test case from r.
func readParseTest(r *bufio.Reader) (text, want, context string, err error) {
line, err := r.ReadSlice('\n')
if err != nil {
return "", "", "", err
}
var b []byte
// Read the HTML.
if string(line) != "#data\n" {
return "", "", "", fmt.Errorf(`got %q want "#data\n"`, line)
}
for {
line, err = r.ReadSlice('\n')
if err != nil {
return "", "", "", err
}
if line[0] == '#' {
break
}
b = append(b, line...)
}
text = strings.TrimSuffix(string(b), "\n")
b = b[:0]
// Skip the error list.
if string(line) != "#errors\n" {
return "", "", "", fmt.Errorf(`got %q want "#errors\n"`, line)
}
for {
line, err = r.ReadSlice('\n')
if err != nil {
return "", "", "", err
}
if line[0] == '#' {
break
}
}
if string(line) == "#document-fragment\n" {
line, err = r.ReadSlice('\n')
if err != nil {
return "", "", "", err
}
context = strings.TrimSpace(string(line))
line, err = r.ReadSlice('\n')
if err != nil {
return "", "", "", err
}
}
// Read the dump of what the parse tree should be.
if string(line) != "#document\n" {
return "", "", "", fmt.Errorf(`got %q want "#document\n"`, line)
}
inQuote := false
for {
line, err = r.ReadSlice('\n')
if err != nil && err != io.EOF {
return "", "", "", err
}
trimmed := bytes.Trim(line, "| \n")
if len(trimmed) > 0 {
if line[0] == '|' && trimmed[0] == '"' {
inQuote = true
}
if trimmed[len(trimmed)-1] == '"' && !(line[0] == '|' && len(trimmed) == 1) {
inQuote = false
}
}
if len(line) == 0 || len(line) == 1 && line[0] == '\n' && !inQuote {
break
}
b = append(b, line...)
}
return text, string(b), context, nil
}
func dumpIndent(w io.Writer, level int) {
io.WriteString(w, "| ")
for i := 0; i < level; i++ {
io.WriteString(w, " ")
}
}
type sortedAttributes []Attribute
func (a sortedAttributes) Len() int {
return len(a)
}
func (a sortedAttributes) Less(i, j int) bool {
if a[i].Namespace != a[j].Namespace {
return a[i].Namespace < a[j].Namespace
}
return a[i].Key < a[j].Key
}
func (a sortedAttributes) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func dumpLevel(w io.Writer, n *Node, level int) error {
dumpIndent(w, level)
switch n.Type {
case ErrorNode:
return errors.New("unexpected ErrorNode")
case DocumentNode:
return errors.New("unexpected DocumentNode")
case ElementNode:
if n.Namespace != "" {
fmt.Fprintf(w, "<%s %s>", n.Namespace, n.Data)
} else {
fmt.Fprintf(w, "<%s>", n.Data)
}
attr := sortedAttributes(n.Attr)
sort.Sort(attr)
for _, a := range attr {
io.WriteString(w, "\n")
dumpIndent(w, level+1)
if a.Namespace != "" {
fmt.Fprintf(w, `%s %s="%s"`, a.Namespace, a.Key, a.Val)
} else {
fmt.Fprintf(w, `%s="%s"`, a.Key, a.Val)
}
}
case TextNode:
fmt.Fprintf(w, `"%s"`, n.Data)
case CommentNode:
fmt.Fprintf(w, "<!-- %s -->", n.Data)
case DoctypeNode:
fmt.Fprintf(w, "<!DOCTYPE %s", n.Data)
if n.Attr != nil {
var p, s string
for _, a := range n.Attr {
switch a.Key {
case "public":
p = a.Val
case "system":
s = a.Val
}
}
if p != "" || s != "" {
fmt.Fprintf(w, ` "%s"`, p)
fmt.Fprintf(w, ` "%s"`, s)
}
}
io.WriteString(w, ">")
case scopeMarkerNode:
return errors.New("unexpected scopeMarkerNode")
default:
return errors.New("unknown node type")
}
io.WriteString(w, "\n")
for c := n.FirstChild; c != nil; c = c.NextSibling {
if err := dumpLevel(w, c, level+1); err != nil {
return err
}
}
return nil
}
func dump(n *Node) (string, error) {
if n == nil || n.FirstChild == nil {
return "", nil
}
var b bytes.Buffer
for c := n.FirstChild; c != nil; c = c.NextSibling {
if err := dumpLevel(&b, c, 0); err != nil {
return "", err
}
}
return b.String(), nil
}
const testDataDir = "testdata/webkit/"
func TestParser(t *testing.T) {
testFiles, err := filepath.Glob(testDataDir + "*.dat")
if err != nil {
t.Fatal(err)
}
for _, tf := range testFiles {
f, err := os.Open(tf)
if err != nil {
t.Fatal(err)
}
defer f.Close()
r := bufio.NewReader(f)
for i := 0; ; i++ {
text, want, context, err := readParseTest(r)
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
err = testParseCase(text, want, context)
if err != nil {
t.Errorf("%s test #%d %q, %s", tf, i, text, err)
}
}
}
}
// testParseCase tests one test case from the test files. If the test does not
// pass, it returns an error that explains the failure.
// text is the HTML to be parsed, want is a dump of the correct parse tree,
// and context is the name of the context node, if any.
func testParseCase(text, want, context string) (err error) {
defer func() {
if x := recover(); x != nil {
switch e := x.(type) {
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
}
}()
var doc *Node
if context == "" {
doc, err = Parse(strings.NewReader(text))
if err != nil {
return err
}
} else {
contextNode := &Node{
Type: ElementNode,
DataAtom: atom.Lookup([]byte(context)),
Data: context,
}
nodes, err := ParseFragment(strings.NewReader(text), contextNode)
if err != nil {
return err
}
doc = &Node{
Type: DocumentNode,
}
for _, n := range nodes {
doc.AppendChild(n)
}
}
if err := checkTreeConsistency(doc); err != nil {
return err
}
got, err := dump(doc)
if err != nil {
return err
}
// Compare the parsed tree to the #document section.
if got != want {
return fmt.Errorf("got vs want:\n----\n%s----\n%s----", got, want)
}
if renderTestBlacklist[text] || context != "" {
return nil
}
// Check that rendering and re-parsing results in an identical tree.
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(Render(pw, doc))
}()
doc1, err := Parse(pr)
if err != nil {
return err
}
got1, err := dump(doc1)
if err != nil {
return err
}
if got != got1 {
return fmt.Errorf("got vs got1:\n----\n%s----\n%s----", got, got1)
}
return nil
}
// Some test input result in parse trees are not 'well-formed' despite
// following the HTML5 recovery algorithms. Rendering and re-parsing such a
// tree will not result in an exact clone of that tree. We blacklist such
// inputs from the render test.
var renderTestBlacklist = map[string]bool{
// The second <a> will be reparented to the first <table>'s parent. This
// results in an <a> whose parent is an <a>, which is not 'well-formed'.
`<a><table><td><a><table></table><a></tr><a></table><b>X</b>C<a>Y`: true,
// The same thing with a <p>:
`<p><table></p>`: true,
// More cases of <a> being reparented:
`<a href="blah">aba<table><a href="foo">br<tr><td></td></tr>x</table>aoe`: true,
`<a><table><a></table><p><a><div><a>`: true,
`<a><table><td><a><table></table><a></tr><a></table><a>`: true,
// A similar reparenting situation involving <nobr>:
`<!DOCTYPE html><body><b><nobr>1<table><nobr></b><i><nobr>2<nobr></i>3`: true,
// A <plaintext> element is reparented, putting it before a table.
// A <plaintext> element can't have anything after it in HTML.
`<table><plaintext><td>`: true,
`<!doctype html><table><plaintext></plaintext>`: true,
`<!doctype html><table><tbody><plaintext></plaintext>`: true,
`<!doctype html><table><tbody><tr><plaintext></plaintext>`: true,
// A form inside a table inside a form doesn't work either.
`<!doctype html><form><table></form><form></table></form>`: true,
// A script that ends at EOF may escape its own closing tag when rendered.
`<!doctype html><script><!--<script `: true,
`<!doctype html><script><!--<script <`: true,
`<!doctype html><script><!--<script <a`: true,
`<!doctype html><script><!--<script </`: true,
`<!doctype html><script><!--<script </s`: true,
`<!doctype html><script><!--<script </script`: true,
`<!doctype html><script><!--<script </scripta`: true,
`<!doctype html><script><!--<script -`: true,
`<!doctype html><script><!--<script -a`: true,
`<!doctype html><script><!--<script -<`: true,
`<!doctype html><script><!--<script --`: true,
`<!doctype html><script><!--<script --a`: true,
`<!doctype html><script><!--<script --<`: true,
`<script><!--<script `: true,
`<script><!--<script <a`: true,
`<script><!--<script </script`: true,
`<script><!--<script </scripta`: true,
`<script><!--<script -`: true,
`<script><!--<script -a`: true,
`<script><!--<script --`: true,
`<script><!--<script --a`: true,
`<script><!--<script <`: true,
`<script><!--<script </`: true,
`<script><!--<script </s`: true,
// Reconstructing the active formatting elements results in a <plaintext>
// element that contains an <a> element.
`<!doctype html><p><a><plaintext>b`: true,
}
func TestNodeConsistency(t *testing.T) {
// inconsistentNode is a Node whose DataAtom and Data do not agree.
inconsistentNode := &Node{
Type: ElementNode,
DataAtom: atom.Frameset,
Data: "table",
}
_, err := ParseFragment(strings.NewReader("<p>hello</p>"), inconsistentNode)
if err == nil {
t.Errorf("got nil error, want non-nil")
}
}
func BenchmarkParser(b *testing.B) {
buf, err := ioutil.ReadFile("testdata/go1.html")
if err != nil {
b.Fatalf("could not read testdata/go1.html: %v", err)
}
b.SetBytes(int64(len(buf)))
runtime.GC()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Parse(bytes.NewBuffer(buf))
}
}

View file

@ -1,156 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import (
"bytes"
"testing"
)
func TestRenderer(t *testing.T) {
nodes := [...]*Node{
0: {
Type: ElementNode,
Data: "html",
},
1: {
Type: ElementNode,
Data: "head",
},
2: {
Type: ElementNode,
Data: "body",
},
3: {
Type: TextNode,
Data: "0<1",
},
4: {
Type: ElementNode,
Data: "p",
Attr: []Attribute{
{
Key: "id",
Val: "A",
},
{
Key: "foo",
Val: `abc"def`,
},
},
},
5: {
Type: TextNode,
Data: "2",
},
6: {
Type: ElementNode,
Data: "b",
Attr: []Attribute{
{
Key: "empty",
Val: "",
},
},
},
7: {
Type: TextNode,
Data: "3",
},
8: {
Type: ElementNode,
Data: "i",
Attr: []Attribute{
{
Key: "backslash",
Val: `\`,
},
},
},
9: {
Type: TextNode,
Data: "&4",
},
10: {
Type: TextNode,
Data: "5",
},
11: {
Type: ElementNode,
Data: "blockquote",
},
12: {
Type: ElementNode,
Data: "br",
},
13: {
Type: TextNode,
Data: "6",
},
}
// Build a tree out of those nodes, based on a textual representation.
// Only the ".\t"s are significant. The trailing HTML-like text is
// just commentary. The "0:" prefixes are for easy cross-reference with
// the nodes array.
treeAsText := [...]string{
0: `<html>`,
1: `. <head>`,
2: `. <body>`,
3: `. . "0&lt;1"`,
4: `. . <p id="A" foo="abc&#34;def">`,
5: `. . . "2"`,
6: `. . . <b empty="">`,
7: `. . . . "3"`,
8: `. . . <i backslash="\">`,
9: `. . . . "&amp;4"`,
10: `. . "5"`,
11: `. . <blockquote>`,
12: `. . <br>`,
13: `. . "6"`,
}
if len(nodes) != len(treeAsText) {
t.Fatal("len(nodes) != len(treeAsText)")
}
var stack [8]*Node
for i, line := range treeAsText {
level := 0
for line[0] == '.' {
// Strip a leading ".\t".
line = line[2:]
level++
}
n := nodes[i]
if level == 0 {
if stack[0] != nil {
t.Fatal("multiple root nodes")
}
stack[0] = n
} else {
stack[level-1].AppendChild(n)
stack[level] = n
for i := level + 1; i < len(stack); i++ {
stack[i] = nil
}
}
// At each stage of tree construction, we check all nodes for consistency.
for j, m := range nodes {
if err := checkNodeConsistency(m); err != nil {
t.Fatalf("i=%d, j=%d: %v", i, j, err)
}
}
}
want := `<html><head></head><body>0&lt;1<p id="A" foo="abc&#34;def">` +
`2<b empty="">3</b><i backslash="\">&amp;4</i></p>` +
`5<blockquote></blockquote><br/>6</body></html>`
b := new(bytes.Buffer)
if err := Render(b, nodes[0]); err != nil {
t.Fatal(err)
}
if got := b.String(); got != want {
t.Errorf("got vs want:\n%s\n%s\n", got, want)
}
}

View file

@ -1161,8 +1161,8 @@ func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool) {
return nil, nil, false
}
// Token returns the next Token. The result's Data and Attr values remain valid
// after subsequent Next calls.
// Token returns the current Token. The result's Data and Attr values remain
// valid after subsequent Next calls.
func (z *Tokenizer) Token() Token {
t := Token{Type: z.tt}
switch z.tt {

View file

@ -1,748 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package html
import (
"bytes"
"io"
"io/ioutil"
"reflect"
"runtime"
"strings"
"testing"
)
type tokenTest struct {
// A short description of the test case.
desc string
// The HTML to parse.
html string
// The string representations of the expected tokens, joined by '$'.
golden string
}
var tokenTests = []tokenTest{
{
"empty",
"",
"",
},
// A single text node. The tokenizer should not break text nodes on whitespace,
// nor should it normalize whitespace within a text node.
{
"text",
"foo bar",
"foo bar",
},
// An entity.
{
"entity",
"one &lt; two",
"one &lt; two",
},
// A start, self-closing and end tag. The tokenizer does not care if the start
// and end tokens don't match; that is the job of the parser.
{
"tags",
"<a>b<c/>d</e>",
"<a>$b$<c/>$d$</e>",
},
// Angle brackets that aren't a tag.
{
"not a tag #0",
"<",
"&lt;",
},
{
"not a tag #1",
"</",
"&lt;/",
},
{
"not a tag #2",
"</>",
"<!---->",
},
{
"not a tag #3",
"a</>b",
"a$<!---->$b",
},
{
"not a tag #4",
"</ >",
"<!-- -->",
},
{
"not a tag #5",
"</.",
"<!--.-->",
},
{
"not a tag #6",
"</.>",
"<!--.-->",
},
{
"not a tag #7",
"a < b",
"a &lt; b",
},
{
"not a tag #8",
"<.>",
"&lt;.&gt;",
},
{
"not a tag #9",
"a<<<b>>>c",
"a&lt;&lt;$<b>$&gt;&gt;c",
},
{
"not a tag #10",
"if x<0 and y < 0 then x*y>0",
"if x&lt;0 and y &lt; 0 then x*y&gt;0",
},
{
"not a tag #11",
"<<p>",
"&lt;$<p>",
},
// EOF in a tag name.
{
"tag name eof #0",
"<a",
"",
},
{
"tag name eof #1",
"<a ",
"",
},
{
"tag name eof #2",
"a<b",
"a",
},
{
"tag name eof #3",
"<a><b",
"<a>",
},
{
"tag name eof #4",
`<a x`,
``,
},
// Some malformed tags that are missing a '>'.
{
"malformed tag #0",
`<p</p>`,
`<p< p="">`,
},
{
"malformed tag #1",
`<p </p>`,
`<p <="" p="">`,
},
{
"malformed tag #2",
`<p id`,
``,
},
{
"malformed tag #3",
`<p id=`,
``,
},
{
"malformed tag #4",
`<p id=>`,
`<p id="">`,
},
{
"malformed tag #5",
`<p id=0`,
``,
},
{
"malformed tag #6",
`<p id=0</p>`,
`<p id="0&lt;/p">`,
},
{
"malformed tag #7",
`<p id="0</p>`,
``,
},
{
"malformed tag #8",
`<p id="0"</p>`,
`<p id="0" <="" p="">`,
},
{
"malformed tag #9",
`<p></p id`,
`<p>`,
},
// Raw text and RCDATA.
{
"basic raw text",
"<script><a></b></script>",
"<script>$&lt;a&gt;&lt;/b&gt;$</script>",
},
{
"unfinished script end tag",
"<SCRIPT>a</SCR",
"<script>$a&lt;/SCR",
},
{
"broken script end tag",
"<SCRIPT>a</SCR ipt>",
"<script>$a&lt;/SCR ipt&gt;",
},
{
"EOF in script end tag",
"<SCRIPT>a</SCRipt",
"<script>$a&lt;/SCRipt",
},
{
"scriptx end tag",
"<SCRIPT>a</SCRiptx",
"<script>$a&lt;/SCRiptx",
},
{
"' ' completes script end tag",
"<SCRIPT>a</SCRipt ",
"<script>$a",
},
{
"'>' completes script end tag",
"<SCRIPT>a</SCRipt>",
"<script>$a$</script>",
},
{
"self-closing script end tag",
"<SCRIPT>a</SCRipt/>",
"<script>$a$</script>",
},
{
"nested script tag",
"<SCRIPT>a</SCRipt<script>",
"<script>$a&lt;/SCRipt&lt;script&gt;",
},
{
"script end tag after unfinished",
"<SCRIPT>a</SCRipt</script>",
"<script>$a&lt;/SCRipt$</script>",
},
{
"script/style mismatched tags",
"<script>a</style>",
"<script>$a&lt;/style&gt;",
},
{
"style element with entity",
"<style>&apos;",
"<style>$&amp;apos;",
},
{
"textarea with tag",
"<textarea><div></textarea>",
"<textarea>$&lt;div&gt;$</textarea>",
},
{
"title with tag and entity",
"<title><b>K&amp;R C</b></title>",
"<title>$&lt;b&gt;K&amp;R C&lt;/b&gt;$</title>",
},
// DOCTYPE tests.
{
"Proper DOCTYPE",
"<!DOCTYPE html>",
"<!DOCTYPE html>",
},
{
"DOCTYPE with no space",
"<!doctypehtml>",
"<!DOCTYPE html>",
},
{
"DOCTYPE with two spaces",
"<!doctype html>",
"<!DOCTYPE html>",
},
{
"looks like DOCTYPE but isn't",
"<!DOCUMENT html>",
"<!--DOCUMENT html-->",
},
{
"DOCTYPE at EOF",
"<!DOCtype",
"<!DOCTYPE >",
},
// XML processing instructions.
{
"XML processing instruction",
"<?xml?>",
"<!--?xml?-->",
},
// Comments.
{
"comment0",
"abc<b><!-- skipme --></b>def",
"abc$<b>$<!-- skipme -->$</b>$def",
},
{
"comment1",
"a<!-->z",
"a$<!---->$z",
},
{
"comment2",
"a<!--->z",
"a$<!---->$z",
},
{
"comment3",
"a<!--x>-->z",
"a$<!--x>-->$z",
},
{
"comment4",
"a<!--x->-->z",
"a$<!--x->-->$z",
},
{
"comment5",
"a<!>z",
"a$<!---->$z",
},
{
"comment6",
"a<!->z",
"a$<!----->$z",
},
{
"comment7",
"a<!---<>z",
"a$<!---<>z-->",
},
{
"comment8",
"a<!--z",
"a$<!--z-->",
},
{
"comment9",
"a<!--z-",
"a$<!--z-->",
},
{
"comment10",
"a<!--z--",
"a$<!--z-->",
},
{
"comment11",
"a<!--z---",
"a$<!--z--->",
},
{
"comment12",
"a<!--z----",
"a$<!--z---->",
},
{
"comment13",
"a<!--x--!>z",
"a$<!--x-->$z",
},
// An attribute with a backslash.
{
"backslash",
`<p id="a\"b">`,
`<p id="a\" b"="">`,
},
// Entities, tag name and attribute key lower-casing, and whitespace
// normalization within a tag.
{
"tricky",
"<p \t\n iD=\"a&quot;B\" foo=\"bar\"><EM>te&lt;&amp;;xt</em></p>",
`<p id="a&#34;B" foo="bar">$<em>$te&lt;&amp;;xt$</em>$</p>`,
},
// A nonexistent entity. Tokenizing and converting back to a string should
// escape the "&" to become "&amp;".
{
"noSuchEntity",
`<a b="c&noSuchEntity;d">&lt;&alsoDoesntExist;&`,
`<a b="c&amp;noSuchEntity;d">$&lt;&amp;alsoDoesntExist;&amp;`,
},
{
"entity without semicolon",
`&notit;&notin;<a b="q=z&amp=5&notice=hello&not;=world">`,
`¬it;∉$<a b="q=z&amp;amp=5&amp;notice=hello¬=world">`,
},
{
"entity with digits",
"&frac12;",
"½",
},
// Attribute tests:
// http://dev.w3.org/html5/pf-summary/Overview.html#attributes
{
"Empty attribute",
`<input disabled FOO>`,
`<input disabled="" foo="">`,
},
{
"Empty attribute, whitespace",
`<input disabled FOO >`,
`<input disabled="" foo="">`,
},
{
"Unquoted attribute value",
`<input value=yes FOO=BAR>`,
`<input value="yes" foo="BAR">`,
},
{
"Unquoted attribute value, spaces",
`<input value = yes FOO = BAR>`,
`<input value="yes" foo="BAR">`,
},
{
"Unquoted attribute value, trailing space",
`<input value=yes FOO=BAR >`,
`<input value="yes" foo="BAR">`,
},
{
"Single-quoted attribute value",
`<input value='yes' FOO='BAR'>`,
`<input value="yes" foo="BAR">`,
},
{
"Single-quoted attribute value, trailing space",
`<input value='yes' FOO='BAR' >`,
`<input value="yes" foo="BAR">`,
},
{
"Double-quoted attribute value",
`<input value="I'm an attribute" FOO="BAR">`,
`<input value="I&#39;m an attribute" foo="BAR">`,
},
{
"Attribute name characters",
`<meta http-equiv="content-type">`,
`<meta http-equiv="content-type">`,
},
{
"Mixed attributes",
`a<P V="0 1" w='2' X=3 y>z`,
`a$<p v="0 1" w="2" x="3" y="">$z`,
},
{
"Attributes with a solitary single quote",
`<p id=can't><p id=won't>`,
`<p id="can&#39;t">$<p id="won&#39;t">`,
},
}
func TestTokenizer(t *testing.T) {
loop:
for _, tt := range tokenTests {
z := NewTokenizer(strings.NewReader(tt.html))
if tt.golden != "" {
for i, s := range strings.Split(tt.golden, "$") {
if z.Next() == ErrorToken {
t.Errorf("%s token %d: want %q got error %v", tt.desc, i, s, z.Err())
continue loop
}
actual := z.Token().String()
if s != actual {
t.Errorf("%s token %d: want %q got %q", tt.desc, i, s, actual)
continue loop
}
}
}
z.Next()
if z.Err() != io.EOF {
t.Errorf("%s: want EOF got %q", tt.desc, z.Err())
}
}
}
func TestMaxBuffer(t *testing.T) {
// Exceeding the maximum buffer size generates ErrBufferExceeded.
z := NewTokenizer(strings.NewReader("<" + strings.Repeat("t", 10)))
z.SetMaxBuf(5)
tt := z.Next()
if got, want := tt, ErrorToken; got != want {
t.Fatalf("token type: got: %v want: %v", got, want)
}
if got, want := z.Err(), ErrBufferExceeded; got != want {
t.Errorf("error type: got: %v want: %v", got, want)
}
if got, want := string(z.Raw()), "<tttt"; got != want {
t.Fatalf("buffered before overflow: got: %q want: %q", got, want)
}
}
func TestMaxBufferReconstruction(t *testing.T) {
// Exceeding the maximum buffer size at any point while tokenizing permits
// reconstructing the original input.
tests:
for _, test := range tokenTests {
for maxBuf := 1; ; maxBuf++ {
r := strings.NewReader(test.html)
z := NewTokenizer(r)
z.SetMaxBuf(maxBuf)
var tokenized bytes.Buffer
for {
tt := z.Next()
tokenized.Write(z.Raw())
if tt == ErrorToken {
if err := z.Err(); err != io.EOF && err != ErrBufferExceeded {
t.Errorf("%s: unexpected error: %v", test.desc, err)
}
break
}
}
// Anything tokenized along with untokenized input or data left in the reader.
assembled, err := ioutil.ReadAll(io.MultiReader(&tokenized, bytes.NewReader(z.Buffered()), r))
if err != nil {
t.Errorf("%s: ReadAll: %v", test.desc, err)
continue tests
}
if got, want := string(assembled), test.html; got != want {
t.Errorf("%s: reassembled html:\n got: %q\nwant: %q", test.desc, got, want)
continue tests
}
// EOF indicates that we completed tokenization and hence found the max
// maxBuf that generates ErrBufferExceeded, so continue to the next test.
if z.Err() == io.EOF {
break
}
} // buffer sizes
} // tests
}
func TestPassthrough(t *testing.T) {
// Accumulating the raw output for each parse event should reconstruct the
// original input.
for _, test := range tokenTests {
z := NewTokenizer(strings.NewReader(test.html))
var parsed bytes.Buffer
for {
tt := z.Next()
parsed.Write(z.Raw())
if tt == ErrorToken {
break
}
}
if got, want := parsed.String(), test.html; got != want {
t.Errorf("%s: parsed output:\n got: %q\nwant: %q", test.desc, got, want)
}
}
}
func TestBufAPI(t *testing.T) {
s := "0<a>1</a>2<b>3<a>4<a>5</a>6</b>7</a>8<a/>9"
z := NewTokenizer(bytes.NewBufferString(s))
var result bytes.Buffer
depth := 0
loop:
for {
tt := z.Next()
switch tt {
case ErrorToken:
if z.Err() != io.EOF {
t.Error(z.Err())
}
break loop
case TextToken:
if depth > 0 {
result.Write(z.Text())
}
case StartTagToken, EndTagToken:
tn, _ := z.TagName()
if len(tn) == 1 && tn[0] == 'a' {
if tt == StartTagToken {
depth++
} else {
depth--
}
}
}
}
u := "14567"
v := string(result.Bytes())
if u != v {
t.Errorf("TestBufAPI: want %q got %q", u, v)
}
}
func TestConvertNewlines(t *testing.T) {
testCases := map[string]string{
"Mac\rDOS\r\nUnix\n": "Mac\nDOS\nUnix\n",
"Unix\nMac\rDOS\r\n": "Unix\nMac\nDOS\n",
"DOS\r\nDOS\r\nDOS\r\n": "DOS\nDOS\nDOS\n",
"": "",
"\n": "\n",
"\n\r": "\n\n",
"\r": "\n",
"\r\n": "\n",
"\r\n\n": "\n\n",
"\r\n\r": "\n\n",
"\r\n\r\n": "\n\n",
"\r\r": "\n\n",
"\r\r\n": "\n\n",
"\r\r\n\n": "\n\n\n",
"\r\r\r\n": "\n\n\n",
"\r \n": "\n \n",
"xyz": "xyz",
}
for in, want := range testCases {
if got := string(convertNewlines([]byte(in))); got != want {
t.Errorf("input %q: got %q, want %q", in, got, want)
}
}
}
func TestReaderEdgeCases(t *testing.T) {
const s = "<p>An io.Reader can return (0, nil) or (n, io.EOF).</p>"
testCases := []io.Reader{
&zeroOneByteReader{s: s},
&eofStringsReader{s: s},
&stuckReader{},
}
for i, tc := range testCases {
got := []TokenType{}
z := NewTokenizer(tc)
for {
tt := z.Next()
if tt == ErrorToken {
break
}
got = append(got, tt)
}
if err := z.Err(); err != nil && err != io.EOF {
if err != io.ErrNoProgress {
t.Errorf("i=%d: %v", i, err)
}
continue
}
want := []TokenType{
StartTagToken,
TextToken,
EndTagToken,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("i=%d: got %v, want %v", i, got, want)
continue
}
}
}
// zeroOneByteReader is like a strings.Reader that alternates between
// returning 0 bytes and 1 byte at a time.
type zeroOneByteReader struct {
s string
n int
}
func (r *zeroOneByteReader) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
if len(r.s) == 0 {
return 0, io.EOF
}
r.n++
if r.n%2 != 0 {
return 0, nil
}
p[0], r.s = r.s[0], r.s[1:]
return 1, nil
}
// eofStringsReader is like a strings.Reader but can return an (n, err) where
// n > 0 && err != nil.
type eofStringsReader struct {
s string
}
func (r *eofStringsReader) Read(p []byte) (int, error) {
n := copy(p, r.s)
r.s = r.s[n:]
if r.s != "" {
return n, nil
}
return n, io.EOF
}
// stuckReader is an io.Reader that always returns no data and no error.
type stuckReader struct{}
func (*stuckReader) Read(p []byte) (int, error) {
return 0, nil
}
const (
rawLevel = iota
lowLevel
highLevel
)
func benchmarkTokenizer(b *testing.B, level int) {
buf, err := ioutil.ReadFile("testdata/go1.html")
if err != nil {
b.Fatalf("could not read testdata/go1.html: %v", err)
}
b.SetBytes(int64(len(buf)))
runtime.GC()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
z := NewTokenizer(bytes.NewBuffer(buf))
for {
tt := z.Next()
if tt == ErrorToken {
if err := z.Err(); err != nil && err != io.EOF {
b.Fatalf("tokenizer error: %v", err)
}
break
}
switch level {
case rawLevel:
// Calling z.Raw just returns the raw bytes of the token. It does
// not unescape &lt; to <, or lower-case tag names and attribute keys.
z.Raw()
case lowLevel:
// Caling z.Text, z.TagName and z.TagAttr returns []byte values
// whose contents may change on the next call to z.Next.
switch tt {
case TextToken, CommentToken, DoctypeToken:
z.Text()
case StartTagToken, SelfClosingTagToken:
_, more := z.TagName()
for more {
_, _, more = z.TagAttr()
}
case EndTagToken:
z.TagName()
}
case highLevel:
// Calling z.Token converts []byte values to strings whose validity
// extend beyond the next call to z.Next.
z.Token()
}
}
}
}
func BenchmarkRawLevelTokenizer(b *testing.B) { benchmarkTokenizer(b, rawLevel) }
func BenchmarkLowLevelTokenizer(b *testing.B) { benchmarkTokenizer(b, lowLevel) }
func BenchmarkHighLevelTokenizer(b *testing.B) { benchmarkTokenizer(b, highLevel) }

65
vendor/golang.org/x/net/http/httpguts/guts.go generated vendored Normal file
View file

@ -0,0 +1,65 @@
// 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.
// Package httpguts provides functions implementing various details
// of the HTTP specification.
//
// This package is shared by the standard library (which vendors it)
// and x/net/http2. It comes with no API stability promise.
package httpguts
import (
"net/textproto"
"strings"
)
// SniffedContentType reports whether ct is a Content-Type that is known
// to cause client-side content sniffing.
//
// This provides just a partial implementation of mime.ParseMediaType
// with the assumption that the Content-Type is not attacker controlled.
func SniffedContentType(ct string) bool {
if i := strings.Index(ct, ";"); i != -1 {
ct = ct[:i]
}
ct = strings.ToLower(strings.TrimSpace(ct))
return ct == "text/plain" || ct == "application/octet-stream" ||
ct == "application/unknown" || ct == "unknown/unknown" || ct == "*/*" ||
!strings.Contains(ct, "/")
}
// ValidTrailerHeader reports whether name is a valid header field name to appear
// in trailers.
// See RFC 7230, Section 4.1.2
func ValidTrailerHeader(name string) bool {
name = textproto.CanonicalMIMEHeaderKey(name)
if strings.HasPrefix(name, "If-") || badTrailer[name] {
return false
}
return true
}
var badTrailer = map[string]bool{
"Authorization": true,
"Cache-Control": true,
"Connection": true,
"Content-Encoding": true,
"Content-Length": true,
"Content-Range": true,
"Content-Type": true,
"Expect": true,
"Host": true,
"Keep-Alive": true,
"Max-Forwards": true,
"Pragma": true,
"Proxy-Authenticate": true,
"Proxy-Authorization": true,
"Proxy-Connection": true,
"Range": true,
"Realm": true,
"Te": true,
"Trailer": true,
"Transfer-Encoding": true,
"Www-Authenticate": true,
}

View file

@ -2,12 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package httplex contains rules around lexical matters of various
// HTTP-related specifications.
//
// This package is shared by the standard library (which vendors it)
// and x/net/http2. It comes with no API stability promise.
package httplex
package httpguts
import (
"net"

View file

@ -5,7 +5,7 @@
package http2
// 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 (
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000

View file

@ -1,309 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import "testing"
func TestIsBadCipherBad(t *testing.T) {
for _, c := range badCiphers {
if !isBadCipher(c) {
t.Errorf("Wrong result for isBadCipher(%d), want true", c)
}
}
}
// verify we don't give false positives on ciphers not on blacklist
func TestIsBadCipherGood(t *testing.T) {
goodCiphers := map[uint16]string{
cipher_TLS_DHE_RSA_WITH_AES_256_CCM: "cipher_TLS_DHE_RSA_WITH_AES_256_CCM",
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM: "cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: "cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
}
for c, name := range goodCiphers {
if isBadCipher(c) {
t.Errorf("Wrong result for isBadCipher(%d) %s, want false", c, name)
}
}
}
// copied from https://http2.github.io/http2-spec/#BadCipherSuites,
var badCiphers = []uint16{
cipher_TLS_NULL_WITH_NULL_NULL,
cipher_TLS_RSA_WITH_NULL_MD5,
cipher_TLS_RSA_WITH_NULL_SHA,
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5,
cipher_TLS_RSA_WITH_RC4_128_MD5,
cipher_TLS_RSA_WITH_RC4_128_SHA,
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
cipher_TLS_RSA_WITH_IDEA_CBC_SHA,
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_RSA_WITH_DES_CBC_SHA,
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA,
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA,
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,
cipher_TLS_DH_anon_WITH_RC4_128_MD5,
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
cipher_TLS_DH_anon_WITH_DES_CBC_SHA,
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_KRB5_WITH_DES_CBC_SHA,
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_KRB5_WITH_RC4_128_SHA,
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA,
cipher_TLS_KRB5_WITH_DES_CBC_MD5,
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5,
cipher_TLS_KRB5_WITH_RC4_128_MD5,
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5,
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA,
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA,
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA,
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5,
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5,
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5,
cipher_TLS_PSK_WITH_NULL_SHA,
cipher_TLS_DHE_PSK_WITH_NULL_SHA,
cipher_TLS_RSA_PSK_WITH_NULL_SHA,
cipher_TLS_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA,
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA,
cipher_TLS_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA,
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA,
cipher_TLS_RSA_WITH_NULL_SHA256,
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256,
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256,
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256,
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA,
cipher_TLS_PSK_WITH_RC4_128_SHA,
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_PSK_WITH_AES_128_CBC_SHA,
cipher_TLS_PSK_WITH_AES_256_CBC_SHA,
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA,
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA,
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
cipher_TLS_RSA_WITH_SEED_CBC_SHA,
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA,
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA,
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA,
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA,
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA,
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256,
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384,
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256,
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384,
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256,
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384,
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256,
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384,
cipher_TLS_PSK_WITH_NULL_SHA256,
cipher_TLS_PSK_WITH_NULL_SHA384,
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
cipher_TLS_DHE_PSK_WITH_NULL_SHA256,
cipher_TLS_DHE_PSK_WITH_NULL_SHA384,
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
cipher_TLS_RSA_PSK_WITH_NULL_SHA256,
cipher_TLS_RSA_PSK_WITH_NULL_SHA384,
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256,
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA,
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDH_RSA_WITH_NULL_SHA,
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA,
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA,
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA,
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDH_anon_WITH_NULL_SHA,
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA,
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA,
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA,
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256,
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384,
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256,
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384,
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256,
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384,
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256,
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384,
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
cipher_TLS_RSA_WITH_AES_128_CCM,
cipher_TLS_RSA_WITH_AES_256_CCM,
cipher_TLS_RSA_WITH_AES_128_CCM_8,
cipher_TLS_RSA_WITH_AES_256_CCM_8,
cipher_TLS_PSK_WITH_AES_128_CCM,
cipher_TLS_PSK_WITH_AES_256_CCM,
cipher_TLS_PSK_WITH_AES_128_CCM_8,
cipher_TLS_PSK_WITH_AES_256_CCM_8,
}

View file

@ -73,7 +73,7 @@ type noDialH2RoundTripper struct{ t *Transport }
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := rt.t.RoundTrip(req)
if err == ErrNoCachedConn {
if isNoCachedConnError(err) {
return nil, http.ErrSkipAltProtocol
}
return res, err

View file

@ -1,157 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.7
package http2
import (
"bytes"
"fmt"
"reflect"
"testing"
)
func fmtDataChunk(chunk []byte) string {
out := ""
var last byte
var count int
for _, c := range chunk {
if c != last {
if count > 0 {
out += fmt.Sprintf(" x %d ", count)
count = 0
}
out += string([]byte{c})
last = c
}
count++
}
if count > 0 {
out += fmt.Sprintf(" x %d", count)
}
return out
}
func fmtDataChunks(chunks [][]byte) string {
var out string
for _, chunk := range chunks {
out += fmt.Sprintf("{%q}", fmtDataChunk(chunk))
}
return out
}
func testDataBuffer(t *testing.T, wantBytes []byte, setup func(t *testing.T) *dataBuffer) {
// Run setup, then read the remaining bytes from the dataBuffer and check
// that they match wantBytes. We use different read sizes to check corner
// cases in Read.
for _, readSize := range []int{1, 2, 1 * 1024, 32 * 1024} {
t.Run(fmt.Sprintf("ReadSize=%d", readSize), func(t *testing.T) {
b := setup(t)
buf := make([]byte, readSize)
var gotRead bytes.Buffer
for {
n, err := b.Read(buf)
gotRead.Write(buf[:n])
if err == errReadEmpty {
break
}
if err != nil {
t.Fatalf("error after %v bytes: %v", gotRead.Len(), err)
}
}
if got, want := gotRead.Bytes(), wantBytes; !bytes.Equal(got, want) {
t.Errorf("FinalRead=%q, want %q", fmtDataChunk(got), fmtDataChunk(want))
}
})
}
}
func TestDataBufferAllocation(t *testing.T) {
writes := [][]byte{
bytes.Repeat([]byte("a"), 1*1024-1),
[]byte("a"),
bytes.Repeat([]byte("b"), 4*1024-1),
[]byte("b"),
bytes.Repeat([]byte("c"), 8*1024-1),
[]byte("c"),
bytes.Repeat([]byte("d"), 16*1024-1),
[]byte("d"),
bytes.Repeat([]byte("e"), 32*1024),
}
var wantRead bytes.Buffer
for _, p := range writes {
wantRead.Write(p)
}
testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer {
b := &dataBuffer{}
for _, p := range writes {
if n, err := b.Write(p); n != len(p) || err != nil {
t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p))
}
}
want := [][]byte{
bytes.Repeat([]byte("a"), 1*1024),
bytes.Repeat([]byte("b"), 4*1024),
bytes.Repeat([]byte("c"), 8*1024),
bytes.Repeat([]byte("d"), 16*1024),
bytes.Repeat([]byte("e"), 16*1024),
bytes.Repeat([]byte("e"), 16*1024),
}
if !reflect.DeepEqual(b.chunks, want) {
t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want))
}
return b
})
}
func TestDataBufferAllocationWithExpected(t *testing.T) {
writes := [][]byte{
bytes.Repeat([]byte("a"), 1*1024), // allocates 16KB
bytes.Repeat([]byte("b"), 14*1024),
bytes.Repeat([]byte("c"), 15*1024), // allocates 16KB more
bytes.Repeat([]byte("d"), 2*1024),
bytes.Repeat([]byte("e"), 1*1024), // overflows 32KB expectation, allocates just 1KB
}
var wantRead bytes.Buffer
for _, p := range writes {
wantRead.Write(p)
}
testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer {
b := &dataBuffer{expected: 32 * 1024}
for _, p := range writes {
if n, err := b.Write(p); n != len(p) || err != nil {
t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p))
}
}
want := [][]byte{
append(bytes.Repeat([]byte("a"), 1*1024), append(bytes.Repeat([]byte("b"), 14*1024), bytes.Repeat([]byte("c"), 1*1024)...)...),
append(bytes.Repeat([]byte("c"), 14*1024), bytes.Repeat([]byte("d"), 2*1024)...),
bytes.Repeat([]byte("e"), 1*1024),
}
if !reflect.DeepEqual(b.chunks, want) {
t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want))
}
return b
})
}
func TestDataBufferWriteAfterPartialRead(t *testing.T) {
testDataBuffer(t, []byte("cdxyz"), func(t *testing.T) *dataBuffer {
b := &dataBuffer{}
if n, err := b.Write([]byte("abcd")); n != 4 || err != nil {
t.Fatalf("Write(\"abcd\")=%v,%v want 4,nil", n, err)
}
p := make([]byte, 2)
if n, err := b.Read(p); n != 2 || err != nil || !bytes.Equal(p, []byte("ab")) {
t.Fatalf("Read()=%q,%v,%v want \"ab\",2,nil", p, n, err)
}
if n, err := b.Write([]byte("xyz")); n != 3 || err != nil {
t.Fatalf("Write(\"xyz\")=%v,%v want 3,nil", n, err)
}
return b
})
}

View file

@ -1,24 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import "testing"
func TestErrCodeString(t *testing.T) {
tests := []struct {
err ErrCode
want string
}{
{ErrCodeProtocol, "PROTOCOL_ERROR"},
{0xd, "HTTP_1_1_REQUIRED"},
{0xf, "unknown error code 0xf"},
}
for i, tt := range tests {
got := tt.err.String()
if got != tt.want {
t.Errorf("%d. Error = %q; want %q", i, got, tt.want)
}
}
}

View file

@ -1,53 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import "testing"
func TestFlow(t *testing.T) {
var st flow
var conn flow
st.add(3)
conn.add(2)
if got, want := st.available(), int32(3); got != want {
t.Errorf("available = %d; want %d", got, want)
}
st.setConnFlow(&conn)
if got, want := st.available(), int32(2); got != want {
t.Errorf("after parent setup, available = %d; want %d", got, want)
}
st.take(2)
if got, want := conn.available(), int32(0); got != want {
t.Errorf("after taking 2, conn = %d; want %d", got, want)
}
if got, want := st.available(), int32(0); got != want {
t.Errorf("after taking 2, stream = %d; want %d", got, want)
}
}
func TestFlowAdd(t *testing.T) {
var f flow
if !f.add(1) {
t.Fatal("failed to add 1")
}
if !f.add(-1) {
t.Fatal("failed to add -1")
}
if got, want := f.available(), int32(0); got != want {
t.Fatalf("size = %d; want %d", got, want)
}
if !f.add(1<<31 - 1) {
t.Fatal("failed to add 2^31-1")
}
if got, want := f.available(), int32(1<<31-1); got != want {
t.Fatalf("size = %d; want %d", got, want)
}
if f.add(1) {
t.Fatal("adding 1 to max shouldn't be allowed")
}
}

View file

@ -14,8 +14,8 @@ import (
"strings"
"sync"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/lex/httplex"
)
const frameHeaderLen = 9
@ -1462,7 +1462,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
if VerboseLogs && fr.logReads {
fr.debugReadLoggerf("http2: decoded hpack field %+v", hf)
}
if !httplex.ValidHeaderFieldValue(hf.Value) {
if !httpguts.ValidHeaderFieldValue(hf.Value) {
invalid = headerFieldValueError(hf.Value)
}
isPseudo := strings.HasPrefix(hf.Name, ":")

File diff suppressed because it is too large Load diff

View file

@ -1,79 +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 (
"crypto/tls"
"net/http"
"testing"
"time"
)
// Tests that http2.Server.IdleTimeout is initialized from
// http.Server.{Idle,Read}Timeout. http.Server.IdleTimeout was
// added in Go 1.8.
func TestConfigureServerIdleTimeout_Go18(t *testing.T) {
const timeout = 5 * time.Second
const notThisOne = 1 * time.Second
// With a zero http2.Server, verify that it copies IdleTimeout:
{
s1 := &http.Server{
IdleTimeout: timeout,
ReadTimeout: notThisOne,
}
s2 := &Server{}
if err := ConfigureServer(s1, s2); err != nil {
t.Fatal(err)
}
if s2.IdleTimeout != timeout {
t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
}
}
// And that it falls back to ReadTimeout:
{
s1 := &http.Server{
ReadTimeout: timeout,
}
s2 := &Server{}
if err := ConfigureServer(s1, s2); err != nil {
t.Fatal(err)
}
if s2.IdleTimeout != timeout {
t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
}
}
// Verify that s1's IdleTimeout doesn't overwrite an existing setting:
{
s1 := &http.Server{
IdleTimeout: notThisOne,
}
s2 := &Server{
IdleTimeout: timeout,
}
if err := ConfigureServer(s1, s2); err != nil {
t.Fatal(err)
}
if s2.IdleTimeout != timeout {
t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
}
}
}
func TestCertClone(t *testing.T) {
c := &tls.Config{
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
panic("shouldn't be called")
},
}
c2 := cloneTLSConfig(c)
if c2.GetClientCertificate == nil {
t.Error("GetClientCertificate is nil")
}
}

View file

@ -1,60 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
package http2
import (
"context"
"net/http"
"reflect"
"testing"
"time"
)
func TestServerGracefulShutdown(t *testing.T) {
var st *serverTester
handlerDone := make(chan struct{})
st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
defer close(handlerDone)
go st.ts.Config.Shutdown(context.Background())
ga := st.wantGoAway()
if ga.ErrCode != ErrCodeNo {
t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode)
}
if ga.LastStreamID != 1 {
t.Errorf("GOAWAY LastStreamID = %v; want 1", ga.LastStreamID)
}
w.Header().Set("x-foo", "bar")
})
defer st.Close()
st.greet()
st.bodylessReq1()
select {
case <-handlerDone:
case <-time.After(5 * time.Second):
t.Fatalf("server did not shutdown?")
}
hf := st.wantHeaders()
goth := st.decodeHeader(hf.HeaderBlockFragment())
wanth := [][2]string{
{":status", "200"},
{"x-foo", "bar"},
{"content-type", "text/plain; charset=utf-8"},
{"content-length", "0"},
}
if !reflect.DeepEqual(goth, wanth) {
t.Errorf("Got headers %v; want %v", goth, wanth)
}
n, err := st.cc.Read([]byte{0})
if n != 0 || err == nil {
t.Errorf("Read = %v, %v; want 0, non-nil", n, err)
}
}

View file

@ -1,33 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"fmt"
"strings"
"testing"
)
func TestGoroutineLock(t *testing.T) {
oldDebug := DebugGoroutines
DebugGoroutines = true
defer func() { DebugGoroutines = oldDebug }()
g := newGoroutineLock()
g.check()
sawPanic := make(chan interface{})
go func() {
defer func() { sawPanic <- recover() }()
g.check() // should panic
}()
e := <-sawPanic
if e == nil {
t.Fatal("did not see panic from check in other goroutine")
}
if !strings.Contains(fmt.Sprint(e), "wrong goroutine") {
t.Errorf("expected on see panic about running on the wrong goroutine; got %v", e)
}
}

View file

@ -206,7 +206,7 @@ func appendVarInt(dst []byte, n byte, i uint64) []byte {
}
// appendHpackString appends s, as encoded in "String Literal"
// representation, to dst and returns the the extended buffer.
// representation, to dst and returns the extended buffer.
//
// s will be encoded in Huffman codes only when it produces strictly
// shorter byte string.

View file

@ -1,386 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package hpack
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
"reflect"
"strings"
"testing"
)
func TestEncoderTableSizeUpdate(t *testing.T) {
tests := []struct {
size1, size2 uint32
wantHex string
}{
// Should emit 2 table size updates (2048 and 4096)
{2048, 4096, "3fe10f 3fe11f 82"},
// Should emit 1 table size update (2048)
{16384, 2048, "3fe10f 82"},
}
for _, tt := range tests {
var buf bytes.Buffer
e := NewEncoder(&buf)
e.SetMaxDynamicTableSize(tt.size1)
e.SetMaxDynamicTableSize(tt.size2)
if err := e.WriteField(pair(":method", "GET")); err != nil {
t.Fatal(err)
}
want := removeSpace(tt.wantHex)
if got := hex.EncodeToString(buf.Bytes()); got != want {
t.Errorf("e.SetDynamicTableSize %v, %v = %q; want %q", tt.size1, tt.size2, got, want)
}
}
}
func TestEncoderWriteField(t *testing.T) {
var buf bytes.Buffer
e := NewEncoder(&buf)
var got []HeaderField
d := NewDecoder(4<<10, func(f HeaderField) {
got = append(got, f)
})
tests := []struct {
hdrs []HeaderField
}{
{[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
}},
{[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
pair("cache-control", "no-cache"),
}},
{[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "https"),
pair(":path", "/index.html"),
pair(":authority", "www.example.com"),
pair("custom-key", "custom-value"),
}},
}
for i, tt := range tests {
buf.Reset()
got = got[:0]
for _, hf := range tt.hdrs {
if err := e.WriteField(hf); err != nil {
t.Fatal(err)
}
}
_, err := d.Write(buf.Bytes())
if err != nil {
t.Errorf("%d. Decoder Write = %v", i, err)
}
if !reflect.DeepEqual(got, tt.hdrs) {
t.Errorf("%d. Decoded %+v; want %+v", i, got, tt.hdrs)
}
}
}
func TestEncoderSearchTable(t *testing.T) {
e := NewEncoder(nil)
e.dynTab.add(pair("foo", "bar"))
e.dynTab.add(pair("blake", "miz"))
e.dynTab.add(pair(":method", "GET"))
tests := []struct {
hf HeaderField
wantI uint64
wantMatch bool
}{
// Name and Value match
{pair("foo", "bar"), uint64(staticTable.len()) + 3, true},
{pair("blake", "miz"), uint64(staticTable.len()) + 2, true},
{pair(":method", "GET"), 2, true},
// Only name match because Sensitive == true. This is allowed to match
// any ":method" entry. The current implementation uses the last entry
// added in newStaticTable.
{HeaderField{":method", "GET", true}, 3, false},
// Only Name matches
{pair("foo", "..."), uint64(staticTable.len()) + 3, false},
{pair("blake", "..."), uint64(staticTable.len()) + 2, false},
// As before, this is allowed to match any ":method" entry.
{pair(":method", "..."), 3, false},
// None match
{pair("foo-", "bar"), 0, false},
}
for _, tt := range tests {
if gotI, gotMatch := e.searchTable(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch {
t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch)
}
}
}
func TestAppendVarInt(t *testing.T) {
tests := []struct {
n byte
i uint64
want []byte
}{
// Fits in a byte:
{1, 0, []byte{0}},
{2, 2, []byte{2}},
{3, 6, []byte{6}},
{4, 14, []byte{14}},
{5, 30, []byte{30}},
{6, 62, []byte{62}},
{7, 126, []byte{126}},
{8, 254, []byte{254}},
// Multiple bytes:
{5, 1337, []byte{31, 154, 10}},
}
for _, tt := range tests {
got := appendVarInt(nil, tt.n, tt.i)
if !bytes.Equal(got, tt.want) {
t.Errorf("appendVarInt(nil, %v, %v) = %v; want %v", tt.n, tt.i, got, tt.want)
}
}
}
func TestAppendHpackString(t *testing.T) {
tests := []struct {
s, wantHex string
}{
// Huffman encoded
{"www.example.com", "8c f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
// Not Huffman encoded
{"a", "01 61"},
// zero length
{"", "00"},
}
for _, tt := range tests {
want := removeSpace(tt.wantHex)
buf := appendHpackString(nil, tt.s)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("appendHpackString(nil, %q) = %q; want %q", tt.s, got, want)
}
}
}
func TestAppendIndexed(t *testing.T) {
tests := []struct {
i uint64
wantHex string
}{
// 1 byte
{1, "81"},
{126, "fe"},
// 2 bytes
{127, "ff00"},
{128, "ff01"},
}
for _, tt := range tests {
want := removeSpace(tt.wantHex)
buf := appendIndexed(nil, tt.i)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("appendIndex(nil, %v) = %q; want %q", tt.i, got, want)
}
}
}
func TestAppendNewName(t *testing.T) {
tests := []struct {
f HeaderField
indexing bool
wantHex string
}{
// Incremental indexing
{HeaderField{"custom-key", "custom-value", false}, true, "40 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
// Without indexing
{HeaderField{"custom-key", "custom-value", false}, false, "00 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
// Never indexed
{HeaderField{"custom-key", "custom-value", true}, true, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
{HeaderField{"custom-key", "custom-value", true}, false, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
}
for _, tt := range tests {
want := removeSpace(tt.wantHex)
buf := appendNewName(nil, tt.f, tt.indexing)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("appendNewName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
}
}
}
func TestAppendIndexedName(t *testing.T) {
tests := []struct {
f HeaderField
i uint64
indexing bool
wantHex string
}{
// Incremental indexing
{HeaderField{":status", "302", false}, 8, true, "48 82 6402"},
// Without indexing
{HeaderField{":status", "302", false}, 8, false, "08 82 6402"},
// Never indexed
{HeaderField{":status", "302", true}, 8, true, "18 82 6402"},
{HeaderField{":status", "302", true}, 8, false, "18 82 6402"},
}
for _, tt := range tests {
want := removeSpace(tt.wantHex)
buf := appendIndexedName(nil, tt.f, tt.i, tt.indexing)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("appendIndexedName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
}
}
}
func TestAppendTableSize(t *testing.T) {
tests := []struct {
i uint32
wantHex string
}{
// Fits into 1 byte
{30, "3e"},
// Extra byte
{31, "3f00"},
{32, "3f01"},
}
for _, tt := range tests {
want := removeSpace(tt.wantHex)
buf := appendTableSize(nil, tt.i)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("appendTableSize(nil, %v) = %q; want %q", tt.i, got, want)
}
}
}
func TestEncoderSetMaxDynamicTableSize(t *testing.T) {
var buf bytes.Buffer
e := NewEncoder(&buf)
tests := []struct {
v uint32
wantUpdate bool
wantMinSize uint32
wantMaxSize uint32
}{
// Set new table size to 2048
{2048, true, 2048, 2048},
// Set new table size to 16384, but still limited to
// 4096
{16384, true, 2048, 4096},
}
for _, tt := range tests {
e.SetMaxDynamicTableSize(tt.v)
if got := e.tableSizeUpdate; tt.wantUpdate != got {
t.Errorf("e.tableSizeUpdate = %v; want %v", got, tt.wantUpdate)
}
if got := e.minSize; tt.wantMinSize != got {
t.Errorf("e.minSize = %v; want %v", got, tt.wantMinSize)
}
if got := e.dynTab.maxSize; tt.wantMaxSize != got {
t.Errorf("e.maxSize = %v; want %v", got, tt.wantMaxSize)
}
}
}
func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
e := NewEncoder(nil)
// 4095 < initialHeaderTableSize means maxSize is truncated to
// 4095.
e.SetMaxDynamicTableSizeLimit(4095)
if got, want := e.dynTab.maxSize, uint32(4095); got != want {
t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
}
if got, want := e.maxSizeLimit, uint32(4095); got != want {
t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
}
if got, want := e.tableSizeUpdate, true; got != want {
t.Errorf("e.tableSizeUpdate = %v; want %v", got, want)
}
// maxSize will be truncated to maxSizeLimit
e.SetMaxDynamicTableSize(16384)
if got, want := e.dynTab.maxSize, uint32(4095); got != want {
t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
}
// 8192 > current maxSizeLimit, so maxSize does not change.
e.SetMaxDynamicTableSizeLimit(8192)
if got, want := e.dynTab.maxSize, uint32(4095); got != want {
t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
}
if got, want := e.maxSizeLimit, uint32(8192); got != want {
t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
}
}
func removeSpace(s string) string {
return strings.Replace(s, " ", "", -1)
}
func BenchmarkEncoderSearchTable(b *testing.B) {
e := NewEncoder(nil)
// A sample of possible header fields.
// This is not based on any actual data from HTTP/2 traces.
var possible []HeaderField
for _, f := range staticTable.ents {
if f.Value == "" {
possible = append(possible, f)
continue
}
// Generate 5 random values, except for cookie and set-cookie,
// which we know can have many values in practice.
num := 5
if f.Name == "cookie" || f.Name == "set-cookie" {
num = 25
}
for i := 0; i < num; i++ {
f.Value = fmt.Sprintf("%s-%d", f.Name, i)
possible = append(possible, f)
}
}
for k := 0; k < 10; k++ {
f := HeaderField{
Name: fmt.Sprintf("x-header-%d", k),
Sensitive: rand.Int()%2 == 0,
}
for i := 0; i < 5; i++ {
f.Value = fmt.Sprintf("%s-%d", f.Name, i)
possible = append(possible, f)
}
}
// Add a random sample to the dynamic table. This very loosely simulates
// a history of 100 requests with 20 header fields per request.
for r := 0; r < 100*20; r++ {
f := possible[rand.Int31n(int32(len(possible)))]
// Skip if this is in the staticTable verbatim.
if _, has := staticTable.search(f); !has {
e.dynTab.add(f)
}
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, f := range possible {
e.searchTable(f)
}
}
}

View file

@ -389,6 +389,12 @@ func (d *Decoder) callEmit(hf HeaderField) error {
// (same invariants and behavior as parseHeaderFieldRepr)
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 {
return DecodingError{errors.New("dynamic table size update MUST occur at the beginning of a header block")}
}
buf := d.buf
size, buf, err := readVarInt(5, buf)
if err != nil {

View file

@ -1,722 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package hpack
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
"reflect"
"strings"
"testing"
"time"
)
func (d *Decoder) mustAt(idx int) HeaderField {
if hf, ok := d.at(uint64(idx)); !ok {
panic(fmt.Sprintf("bogus index %d", idx))
} else {
return hf
}
}
func TestDynamicTableAt(t *testing.T) {
d := NewDecoder(4096, nil)
at := d.mustAt
if got, want := at(2), (pair(":method", "GET")); got != want {
t.Errorf("at(2) = %v; want %v", got, want)
}
d.dynTab.add(pair("foo", "bar"))
d.dynTab.add(pair("blake", "miz"))
if got, want := at(staticTable.len()+1), (pair("blake", "miz")); got != want {
t.Errorf("at(dyn 1) = %v; want %v", got, want)
}
if got, want := at(staticTable.len()+2), (pair("foo", "bar")); got != want {
t.Errorf("at(dyn 2) = %v; want %v", got, want)
}
if got, want := at(3), (pair(":method", "POST")); got != want {
t.Errorf("at(3) = %v; want %v", got, want)
}
}
func TestDynamicTableSizeEvict(t *testing.T) {
d := NewDecoder(4096, nil)
if want := uint32(0); d.dynTab.size != want {
t.Fatalf("size = %d; want %d", d.dynTab.size, want)
}
add := d.dynTab.add
add(pair("blake", "eats pizza"))
if want := uint32(15 + 32); d.dynTab.size != want {
t.Fatalf("after pizza, size = %d; want %d", d.dynTab.size, want)
}
add(pair("foo", "bar"))
if want := uint32(15 + 32 + 6 + 32); d.dynTab.size != want {
t.Fatalf("after foo bar, size = %d; want %d", d.dynTab.size, want)
}
d.dynTab.setMaxSize(15 + 32 + 1 /* slop */)
if want := uint32(6 + 32); d.dynTab.size != want {
t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
}
if got, want := d.mustAt(staticTable.len()+1), (pair("foo", "bar")); got != want {
t.Errorf("at(dyn 1) = %v; want %v", got, want)
}
add(pair("long", strings.Repeat("x", 500)))
if want := uint32(0); d.dynTab.size != want {
t.Fatalf("after big one, size = %d; want %d", d.dynTab.size, want)
}
}
func TestDecoderDecode(t *testing.T) {
tests := []struct {
name string
in []byte
want []HeaderField
wantDynTab []HeaderField // newest entry first
}{
// C.2.1 Literal Header Field with Indexing
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.1
{"C.2.1", dehex("400a 6375 7374 6f6d 2d6b 6579 0d63 7573 746f 6d2d 6865 6164 6572"),
[]HeaderField{pair("custom-key", "custom-header")},
[]HeaderField{pair("custom-key", "custom-header")},
},
// C.2.2 Literal Header Field without Indexing
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.2
{"C.2.2", dehex("040c 2f73 616d 706c 652f 7061 7468"),
[]HeaderField{pair(":path", "/sample/path")},
[]HeaderField{}},
// C.2.3 Literal Header Field never Indexed
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.3
{"C.2.3", dehex("1008 7061 7373 776f 7264 0673 6563 7265 74"),
[]HeaderField{{"password", "secret", true}},
[]HeaderField{}},
// C.2.4 Indexed Header Field
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.4
{"C.2.4", []byte("\x82"),
[]HeaderField{pair(":method", "GET")},
[]HeaderField{}},
}
for _, tt := range tests {
d := NewDecoder(4096, nil)
hf, err := d.DecodeFull(tt.in)
if err != nil {
t.Errorf("%s: %v", tt.name, err)
continue
}
if !reflect.DeepEqual(hf, tt.want) {
t.Errorf("%s: Got %v; want %v", tt.name, hf, tt.want)
}
gotDynTab := d.dynTab.reverseCopy()
if !reflect.DeepEqual(gotDynTab, tt.wantDynTab) {
t.Errorf("%s: dynamic table after = %v; want %v", tt.name, gotDynTab, tt.wantDynTab)
}
}
}
func (dt *dynamicTable) reverseCopy() (hf []HeaderField) {
hf = make([]HeaderField, len(dt.table.ents))
for i := range hf {
hf[i] = dt.table.ents[len(dt.table.ents)-1-i]
}
return
}
type encAndWant struct {
enc []byte
want []HeaderField
wantDynTab []HeaderField
wantDynSize uint32
}
// C.3 Request Examples without Huffman Coding
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.3
func TestDecodeC3_NoHuffman(t *testing.T) {
testDecodeSeries(t, 4096, []encAndWant{
{dehex("8286 8441 0f77 7777 2e65 7861 6d70 6c65 2e63 6f6d"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
},
[]HeaderField{
pair(":authority", "www.example.com"),
},
57,
},
{dehex("8286 84be 5808 6e6f 2d63 6163 6865"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
pair("cache-control", "no-cache"),
},
[]HeaderField{
pair("cache-control", "no-cache"),
pair(":authority", "www.example.com"),
},
110,
},
{dehex("8287 85bf 400a 6375 7374 6f6d 2d6b 6579 0c63 7573 746f 6d2d 7661 6c75 65"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "https"),
pair(":path", "/index.html"),
pair(":authority", "www.example.com"),
pair("custom-key", "custom-value"),
},
[]HeaderField{
pair("custom-key", "custom-value"),
pair("cache-control", "no-cache"),
pair(":authority", "www.example.com"),
},
164,
},
})
}
// C.4 Request Examples with Huffman Coding
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.4
func TestDecodeC4_Huffman(t *testing.T) {
testDecodeSeries(t, 4096, []encAndWant{
{dehex("8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
},
[]HeaderField{
pair(":authority", "www.example.com"),
},
57,
},
{dehex("8286 84be 5886 a8eb 1064 9cbf"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "http"),
pair(":path", "/"),
pair(":authority", "www.example.com"),
pair("cache-control", "no-cache"),
},
[]HeaderField{
pair("cache-control", "no-cache"),
pair(":authority", "www.example.com"),
},
110,
},
{dehex("8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 a849 e95b b8e8 b4bf"),
[]HeaderField{
pair(":method", "GET"),
pair(":scheme", "https"),
pair(":path", "/index.html"),
pair(":authority", "www.example.com"),
pair("custom-key", "custom-value"),
},
[]HeaderField{
pair("custom-key", "custom-value"),
pair("cache-control", "no-cache"),
pair(":authority", "www.example.com"),
},
164,
},
})
}
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.5
// "This section shows several consecutive header lists, corresponding
// to HTTP responses, on the same connection. The HTTP/2 setting
// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
// octets, causing some evictions to occur."
func TestDecodeC5_ResponsesNoHuff(t *testing.T) {
testDecodeSeries(t, 256, []encAndWant{
{dehex(`
4803 3330 3258 0770 7269 7661 7465 611d
4d6f 6e2c 2032 3120 4f63 7420 3230 3133
2032 303a 3133 3a32 3120 474d 546e 1768
7474 7073 3a2f 2f77 7777 2e65 7861 6d70
6c65 2e63 6f6d
`),
[]HeaderField{
pair(":status", "302"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("location", "https://www.example.com"),
},
[]HeaderField{
pair("location", "https://www.example.com"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("cache-control", "private"),
pair(":status", "302"),
},
222,
},
{dehex("4803 3330 37c1 c0bf"),
[]HeaderField{
pair(":status", "307"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("location", "https://www.example.com"),
},
[]HeaderField{
pair(":status", "307"),
pair("location", "https://www.example.com"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("cache-control", "private"),
},
222,
},
{dehex(`
88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
3230 3133 2032 303a 3133 3a32 3220 474d
54c0 5a04 677a 6970 7738 666f 6f3d 4153
444a 4b48 514b 425a 584f 5157 454f 5049
5541 5851 5745 4f49 553b 206d 6178 2d61
6765 3d33 3630 303b 2076 6572 7369 6f6e
3d31
`),
[]HeaderField{
pair(":status", "200"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
pair("location", "https://www.example.com"),
pair("content-encoding", "gzip"),
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
},
[]HeaderField{
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
pair("content-encoding", "gzip"),
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
},
215,
},
})
}
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.6
// "This section shows the same examples as the previous section, but
// using Huffman encoding for the literal values. The HTTP/2 setting
// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
// octets, causing some evictions to occur. The eviction mechanism
// uses the length of the decoded literal values, so the same
// evictions occurs as in the previous section."
func TestDecodeC6_ResponsesHuffman(t *testing.T) {
testDecodeSeries(t, 256, []encAndWant{
{dehex(`
4882 6402 5885 aec3 771a 4b61 96d0 7abe
9410 54d4 44a8 2005 9504 0b81 66e0 82a6
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
e9ae 82ae 43d3
`),
[]HeaderField{
pair(":status", "302"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("location", "https://www.example.com"),
},
[]HeaderField{
pair("location", "https://www.example.com"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("cache-control", "private"),
pair(":status", "302"),
},
222,
},
{dehex("4883 640e ffc1 c0bf"),
[]HeaderField{
pair(":status", "307"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("location", "https://www.example.com"),
},
[]HeaderField{
pair(":status", "307"),
pair("location", "https://www.example.com"),
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
pair("cache-control", "private"),
},
222,
},
{dehex(`
88c1 6196 d07a be94 1054 d444 a820 0595
040b 8166 e084 a62d 1bff c05a 839b d9ab
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
9587 3160 65c0 03ed 4ee5 b106 3d50 07
`),
[]HeaderField{
pair(":status", "200"),
pair("cache-control", "private"),
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
pair("location", "https://www.example.com"),
pair("content-encoding", "gzip"),
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
},
[]HeaderField{
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
pair("content-encoding", "gzip"),
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
},
215,
},
})
}
func testDecodeSeries(t *testing.T, size uint32, steps []encAndWant) {
d := NewDecoder(size, nil)
for i, step := range steps {
hf, err := d.DecodeFull(step.enc)
if err != nil {
t.Fatalf("Error at step index %d: %v", i, err)
}
if !reflect.DeepEqual(hf, step.want) {
t.Fatalf("At step index %d: Got headers %v; want %v", i, hf, step.want)
}
gotDynTab := d.dynTab.reverseCopy()
if !reflect.DeepEqual(gotDynTab, step.wantDynTab) {
t.Errorf("After step index %d, dynamic table = %v; want %v", i, gotDynTab, step.wantDynTab)
}
if d.dynTab.size != step.wantDynSize {
t.Errorf("After step index %d, dynamic table size = %v; want %v", i, d.dynTab.size, step.wantDynSize)
}
}
}
func TestHuffmanDecodeExcessPadding(t *testing.T) {
tests := [][]byte{
{0xff}, // Padding Exceeds 7 bits
{0x1f, 0xff}, // {"a", 1 byte excess padding}
{0x1f, 0xff, 0xff}, // {"a", 2 byte excess padding}
{0x1f, 0xff, 0xff, 0xff}, // {"a", 3 byte excess padding}
{0xff, 0x9f, 0xff, 0xff, 0xff}, // {"a", 29 bit excess padding}
{'R', 0xbc, '0', 0xff, 0xff, 0xff, 0xff}, // Padding ends on partial symbol.
}
for i, in := range tests {
var buf bytes.Buffer
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
t.Errorf("test-%d: decode(%q) = %v; want ErrInvalidHuffman", i, in, err)
}
}
}
func TestHuffmanDecodeEOS(t *testing.T) {
in := []byte{0xff, 0xff, 0xff, 0xff, 0xfc} // {EOS, "?"}
var buf bytes.Buffer
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
t.Errorf("error = %v; want ErrInvalidHuffman", err)
}
}
func TestHuffmanDecodeMaxLengthOnTrailingByte(t *testing.T) {
in := []byte{0x00, 0x01} // {"0", "0", "0"}
var buf bytes.Buffer
if err := huffmanDecode(&buf, 2, in); err != ErrStringLength {
t.Errorf("error = %v; want ErrStringLength", err)
}
}
func TestHuffmanDecodeCorruptPadding(t *testing.T) {
in := []byte{0x00}
var buf bytes.Buffer
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
t.Errorf("error = %v; want ErrInvalidHuffman", err)
}
}
func TestHuffmanDecode(t *testing.T) {
tests := []struct {
inHex, want string
}{
{"f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com"},
{"a8eb 1064 9cbf", "no-cache"},
{"25a8 49e9 5ba9 7d7f", "custom-key"},
{"25a8 49e9 5bb8 e8b4 bf", "custom-value"},
{"6402", "302"},
{"aec3 771a 4b", "private"},
{"d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
{"9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3", "https://www.example.com"},
{"9bd9 ab", "gzip"},
{"94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07",
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
}
for i, tt := range tests {
var buf bytes.Buffer
in, err := hex.DecodeString(strings.Replace(tt.inHex, " ", "", -1))
if err != nil {
t.Errorf("%d. hex input error: %v", i, err)
continue
}
if _, err := HuffmanDecode(&buf, in); err != nil {
t.Errorf("%d. decode error: %v", i, err)
continue
}
if got := buf.String(); tt.want != got {
t.Errorf("%d. decode = %q; want %q", i, got, tt.want)
}
}
}
func TestAppendHuffmanString(t *testing.T) {
tests := []struct {
in, want string
}{
{"www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
{"no-cache", "a8eb 1064 9cbf"},
{"custom-key", "25a8 49e9 5ba9 7d7f"},
{"custom-value", "25a8 49e9 5bb8 e8b4 bf"},
{"302", "6402"},
{"private", "aec3 771a 4b"},
{"Mon, 21 Oct 2013 20:13:21 GMT", "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff"},
{"https://www.example.com", "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3"},
{"gzip", "9bd9 ab"},
{"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
"94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07"},
}
for i, tt := range tests {
buf := []byte{}
want := strings.Replace(tt.want, " ", "", -1)
buf = AppendHuffmanString(buf, tt.in)
if got := hex.EncodeToString(buf); want != got {
t.Errorf("%d. encode = %q; want %q", i, got, want)
}
}
}
func TestHuffmanMaxStrLen(t *testing.T) {
const msg = "Some string"
huff := AppendHuffmanString(nil, msg)
testGood := func(max int) {
var out bytes.Buffer
if err := huffmanDecode(&out, max, huff); err != nil {
t.Errorf("For maxLen=%d, unexpected error: %v", max, err)
}
if out.String() != msg {
t.Errorf("For maxLen=%d, out = %q; want %q", max, out.String(), msg)
}
}
testGood(0)
testGood(len(msg))
testGood(len(msg) + 1)
var out bytes.Buffer
if err := huffmanDecode(&out, len(msg)-1, huff); err != ErrStringLength {
t.Errorf("err = %v; want ErrStringLength", err)
}
}
func TestHuffmanRoundtripStress(t *testing.T) {
const Len = 50 // of uncompressed string
input := make([]byte, Len)
var output bytes.Buffer
var huff []byte
n := 5000
if testing.Short() {
n = 100
}
seed := time.Now().UnixNano()
t.Logf("Seed = %v", seed)
src := rand.New(rand.NewSource(seed))
var encSize int64
for i := 0; i < n; i++ {
for l := range input {
input[l] = byte(src.Intn(256))
}
huff = AppendHuffmanString(huff[:0], string(input))
encSize += int64(len(huff))
output.Reset()
if err := huffmanDecode(&output, 0, huff); err != nil {
t.Errorf("Failed to decode %q -> %q -> error %v", input, huff, err)
continue
}
if !bytes.Equal(output.Bytes(), input) {
t.Errorf("Roundtrip failure on %q -> %q -> %q", input, huff, output.Bytes())
}
}
t.Logf("Compressed size of original: %0.02f%% (%v -> %v)", 100*(float64(encSize)/(Len*float64(n))), Len*n, encSize)
}
func TestHuffmanDecodeFuzz(t *testing.T) {
const Len = 50 // of compressed
var buf, zbuf bytes.Buffer
n := 5000
if testing.Short() {
n = 100
}
seed := time.Now().UnixNano()
t.Logf("Seed = %v", seed)
src := rand.New(rand.NewSource(seed))
numFail := 0
for i := 0; i < n; i++ {
zbuf.Reset()
if i == 0 {
// Start with at least one invalid one.
zbuf.WriteString("00\x91\xff\xff\xff\xff\xc8")
} else {
for l := 0; l < Len; l++ {
zbuf.WriteByte(byte(src.Intn(256)))
}
}
buf.Reset()
if err := huffmanDecode(&buf, 0, zbuf.Bytes()); err != nil {
if err == ErrInvalidHuffman {
numFail++
continue
}
t.Errorf("Failed to decode %q: %v", zbuf.Bytes(), err)
continue
}
}
t.Logf("%0.02f%% are invalid (%d / %d)", 100*float64(numFail)/float64(n), numFail, n)
if numFail < 1 {
t.Error("expected at least one invalid huffman encoding (test starts with one)")
}
}
func TestReadVarInt(t *testing.T) {
type res struct {
i uint64
consumed int
err error
}
tests := []struct {
n byte
p []byte
want res
}{
// Fits in a byte:
{1, []byte{0}, res{0, 1, nil}},
{2, []byte{2}, res{2, 1, nil}},
{3, []byte{6}, res{6, 1, nil}},
{4, []byte{14}, res{14, 1, nil}},
{5, []byte{30}, res{30, 1, nil}},
{6, []byte{62}, res{62, 1, nil}},
{7, []byte{126}, res{126, 1, nil}},
{8, []byte{254}, res{254, 1, nil}},
// Doesn't fit in a byte:
{1, []byte{1}, res{0, 0, errNeedMore}},
{2, []byte{3}, res{0, 0, errNeedMore}},
{3, []byte{7}, res{0, 0, errNeedMore}},
{4, []byte{15}, res{0, 0, errNeedMore}},
{5, []byte{31}, res{0, 0, errNeedMore}},
{6, []byte{63}, res{0, 0, errNeedMore}},
{7, []byte{127}, res{0, 0, errNeedMore}},
{8, []byte{255}, res{0, 0, errNeedMore}},
// Ignoring top bits:
{5, []byte{255, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 111
{5, []byte{159, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 100
{5, []byte{191, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 101
// Extra byte:
{5, []byte{191, 154, 10, 2}, res{1337, 3, nil}}, // extra byte
// Short a byte:
{5, []byte{191, 154}, res{0, 0, errNeedMore}},
// integer overflow:
{1, []byte{255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, res{0, 0, errVarintOverflow}},
}
for _, tt := range tests {
i, remain, err := readVarInt(tt.n, tt.p)
consumed := len(tt.p) - len(remain)
got := res{i, consumed, err}
if got != tt.want {
t.Errorf("readVarInt(%d, %v ~ %x) = %+v; want %+v", tt.n, tt.p, tt.p, got, tt.want)
}
}
}
// Fuzz crash, originally reported at https://github.com/bradfitz/http2/issues/56
func TestHuffmanFuzzCrash(t *testing.T) {
got, err := HuffmanDecodeToString([]byte("00\x91\xff\xff\xff\xff\xc8"))
if got != "" {
t.Errorf("Got %q; want empty string", got)
}
if err != ErrInvalidHuffman {
t.Errorf("Err = %v; want ErrInvalidHuffman", err)
}
}
func pair(name, value string) HeaderField {
return HeaderField{Name: name, Value: value}
}
func dehex(s string) []byte {
s = strings.Replace(s, " ", "", -1)
s = strings.Replace(s, "\n", "", -1)
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
func TestEmitEnabled(t *testing.T) {
var buf bytes.Buffer
enc := NewEncoder(&buf)
enc.WriteField(HeaderField{Name: "foo", Value: "bar"})
enc.WriteField(HeaderField{Name: "foo", Value: "bar"})
numCallback := 0
var dec *Decoder
dec = NewDecoder(8<<20, func(HeaderField) {
numCallback++
dec.SetEmitEnabled(false)
})
if !dec.EmitEnabled() {
t.Errorf("initial emit enabled = false; want true")
}
if _, err := dec.Write(buf.Bytes()); err != nil {
t.Error(err)
}
if numCallback != 1 {
t.Errorf("num callbacks = %d; want 1", numCallback)
}
if dec.EmitEnabled() {
t.Errorf("emit enabled = true; want false")
}
}
func TestSaveBufLimit(t *testing.T) {
const maxStr = 1 << 10
var got []HeaderField
dec := NewDecoder(initialHeaderTableSize, func(hf HeaderField) {
got = append(got, hf)
})
dec.SetMaxStringLength(maxStr)
var frag []byte
frag = append(frag[:0], encodeTypeByte(false, false))
frag = appendVarInt(frag, 7, 3)
frag = append(frag, "foo"...)
frag = appendVarInt(frag, 7, 3)
frag = append(frag, "bar"...)
if _, err := dec.Write(frag); err != nil {
t.Fatal(err)
}
want := []HeaderField{{Name: "foo", Value: "bar"}}
if !reflect.DeepEqual(got, want) {
t.Errorf("After small writes, got %v; want %v", got, want)
}
frag = append(frag[:0], encodeTypeByte(false, false))
frag = appendVarInt(frag, 7, maxStr*3)
frag = append(frag, make([]byte, maxStr*3)...)
_, err := dec.Write(frag)
if err != ErrStringLength {
t.Fatalf("Write error = %v; want ErrStringLength", err)
}
}

View file

@ -1,214 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package hpack
import (
"bufio"
"regexp"
"strconv"
"strings"
"testing"
)
func TestHeaderFieldTable(t *testing.T) {
table := &headerFieldTable{}
table.init()
table.addEntry(pair("key1", "value1-1"))
table.addEntry(pair("key2", "value2-1"))
table.addEntry(pair("key1", "value1-2"))
table.addEntry(pair("key3", "value3-1"))
table.addEntry(pair("key4", "value4-1"))
table.addEntry(pair("key2", "value2-2"))
// Tests will be run twice: once before evicting anything, and
// again after evicting the three oldest entries.
tests := []struct {
f HeaderField
beforeWantStaticI uint64
beforeWantMatch bool
afterWantStaticI uint64
afterWantMatch bool
}{
{HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
{HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
{HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
{HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
{HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
{HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
{HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
// Name match only, because sensitive.
{HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
// Key not found.
{HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
}
staticToDynamic := func(i uint64) uint64 {
if i == 0 {
return 0
}
return uint64(table.len()) - i + 1 // dynamic is the reversed table
}
searchStatic := func(f HeaderField) (uint64, bool) {
old := staticTable
staticTable = table
defer func() { staticTable = old }()
return staticTable.search(f)
}
searchDynamic := func(f HeaderField) (uint64, bool) {
return table.search(f)
}
for _, test := range tests {
gotI, gotMatch := searchStatic(test.f)
if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
}
gotI, gotMatch = searchDynamic(test.f)
wantDynamicI := staticToDynamic(test.beforeWantStaticI)
if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
}
}
table.evictOldest(3)
for _, test := range tests {
gotI, gotMatch := searchStatic(test.f)
if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
}
gotI, gotMatch = searchDynamic(test.f)
wantDynamicI := staticToDynamic(test.afterWantStaticI)
if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
}
}
}
func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
table := &headerFieldTable{}
table.init()
table.addEntry(pair("key1", "value1-1"))
table.addEntry(pair("key2", "value2-1"))
table.addEntry(pair("key1", "value1-2"))
table.addEntry(pair("key3", "value3-1"))
table.addEntry(pair("key4", "value4-1"))
table.addEntry(pair("key2", "value2-2"))
// evict all pairs
table.evictOldest(table.len())
if l := table.len(); l > 0 {
t.Errorf("table.len() = %d, want 0", l)
}
if l := len(table.byName); l > 0 {
t.Errorf("len(table.byName) = %d, want 0", l)
}
if l := len(table.byNameValue); l > 0 {
t.Errorf("len(table.byNameValue) = %d, want 0", l)
}
}
func TestStaticTable(t *testing.T) {
fromSpec := `
+-------+-----------------------------+---------------+
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| 9 | :status | 204 |
| 10 | :status | 206 |
| 11 | :status | 304 |
| 12 | :status | 400 |
| 13 | :status | 404 |
| 14 | :status | 500 |
| 15 | accept-charset | |
| 16 | accept-encoding | gzip, deflate |
| 17 | accept-language | |
| 18 | accept-ranges | |
| 19 | accept | |
| 20 | access-control-allow-origin | |
| 21 | age | |
| 22 | allow | |
| 23 | authorization | |
| 24 | cache-control | |
| 25 | content-disposition | |
| 26 | content-encoding | |
| 27 | content-language | |
| 28 | content-length | |
| 29 | content-location | |
| 30 | content-range | |
| 31 | content-type | |
| 32 | cookie | |
| 33 | date | |
| 34 | etag | |
| 35 | expect | |
| 36 | expires | |
| 37 | from | |
| 38 | host | |
| 39 | if-match | |
| 40 | if-modified-since | |
| 41 | if-none-match | |
| 42 | if-range | |
| 43 | if-unmodified-since | |
| 44 | last-modified | |
| 45 | link | |
| 46 | location | |
| 47 | max-forwards | |
| 48 | proxy-authenticate | |
| 49 | proxy-authorization | |
| 50 | range | |
| 51 | referer | |
| 52 | refresh | |
| 53 | retry-after | |
| 54 | server | |
| 55 | set-cookie | |
| 56 | strict-transport-security | |
| 57 | transfer-encoding | |
| 58 | user-agent | |
| 59 | vary | |
| 60 | via | |
| 61 | www-authenticate | |
+-------+-----------------------------+---------------+
`
bs := bufio.NewScanner(strings.NewReader(fromSpec))
re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
for bs.Scan() {
l := bs.Text()
if !strings.Contains(l, "|") {
continue
}
m := re.FindStringSubmatch(l)
if m == nil {
continue
}
i, err := strconv.Atoi(m[1])
if err != nil {
t.Errorf("Bogus integer on line %q", l)
continue
}
if i < 1 || i > staticTable.len() {
t.Errorf("Bogus index %d on line %q", i, l)
continue
}
if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
t.Errorf("header index %d name = %q; want %q", i, got, want)
}
if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
t.Errorf("header index %d value = %q; want %q", i, got, want)
}
}
if err := bs.Err(); err != nil {
t.Error(err)
}
}

View file

@ -29,7 +29,7 @@ import (
"strings"
"sync"
"golang.org/x/net/lex/httplex"
"golang.org/x/net/http/httpguts"
)
var (
@ -179,7 +179,7 @@ var (
)
// validWireHeaderFieldName reports whether v is a valid header field
// name (key). See httplex.ValidHeaderName for the base rules.
// 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
@ -191,7 +191,7 @@ func validWireHeaderFieldName(v string) bool {
return false
}
for _, r := range v {
if !httplex.IsTokenRune(r) {
if !httpguts.IsTokenRune(r) {
return false
}
if 'A' <= r && r <= 'Z' {
@ -312,7 +312,7 @@ func mustUint31(v int32) uint32 {
}
// bodyAllowedForStatus reports whether a given response status code
// permits a body. See RFC 2616, section 4.4.
// permits a body. See RFC 7230, section 3.3.
func bodyAllowedForStatus(status int) bool {
switch {
case status >= 100 && status <= 199:

View file

@ -1,199 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"bytes"
"errors"
"flag"
"fmt"
"net/http"
"os/exec"
"strconv"
"strings"
"testing"
"golang.org/x/net/http2/hpack"
)
var knownFailing = flag.Bool("known_failing", false, "Run known-failing tests.")
func condSkipFailingTest(t *testing.T) {
if !*knownFailing {
t.Skip("Skipping known-failing test without --known_failing")
}
}
func init() {
inTests = true
DebugGoroutines = true
flag.BoolVar(&VerboseLogs, "verboseh2", VerboseLogs, "Verbose HTTP/2 debug logging")
}
func TestSettingString(t *testing.T) {
tests := []struct {
s Setting
want string
}{
{Setting{SettingMaxFrameSize, 123}, "[MAX_FRAME_SIZE = 123]"},
{Setting{1<<16 - 1, 123}, "[UNKNOWN_SETTING_65535 = 123]"},
}
for i, tt := range tests {
got := fmt.Sprint(tt.s)
if got != tt.want {
t.Errorf("%d. for %#v, string = %q; want %q", i, tt.s, got, tt.want)
}
}
}
type twriter struct {
t testing.TB
st *serverTester // optional
}
func (w twriter) Write(p []byte) (n int, err error) {
if w.st != nil {
ps := string(p)
for _, phrase := range w.st.logFilter {
if strings.Contains(ps, phrase) {
return len(p), nil // no logging
}
}
}
w.t.Logf("%s", p)
return len(p), nil
}
// like encodeHeader, but don't add implicit pseudo headers.
func encodeHeaderNoImplicit(t *testing.T, headers ...string) []byte {
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
for len(headers) > 0 {
k, v := headers[0], headers[1]
headers = headers[2:]
if err := enc.WriteField(hpack.HeaderField{Name: k, Value: v}); err != nil {
t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
}
}
return buf.Bytes()
}
// Verify that curl has http2.
func requireCurl(t *testing.T) {
out, err := dockerLogs(curl(t, "--version"))
if err != nil {
t.Skipf("failed to determine curl features; skipping test")
}
if !strings.Contains(string(out), "HTTP2") {
t.Skip("curl doesn't support HTTP2; skipping test")
}
}
func curl(t *testing.T, args ...string) (container string) {
out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "gohttp2/curl"}, args...)...).Output()
if err != nil {
t.Skipf("Failed to run curl in docker: %v, %s", err, out)
}
return strings.TrimSpace(string(out))
}
// Verify that h2load exists.
func requireH2load(t *testing.T) {
out, err := dockerLogs(h2load(t, "--version"))
if err != nil {
t.Skipf("failed to probe h2load; skipping test: %s", out)
}
if !strings.Contains(string(out), "h2load nghttp2/") {
t.Skipf("h2load not present; skipping test. (Output=%q)", out)
}
}
func h2load(t *testing.T, args ...string) (container string) {
out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl"}, args...)...).Output()
if err != nil {
t.Skipf("Failed to run h2load in docker: %v, %s", err, out)
}
return strings.TrimSpace(string(out))
}
type puppetCommand struct {
fn func(w http.ResponseWriter, r *http.Request)
done chan<- bool
}
type handlerPuppet struct {
ch chan puppetCommand
}
func newHandlerPuppet() *handlerPuppet {
return &handlerPuppet{
ch: make(chan puppetCommand),
}
}
func (p *handlerPuppet) act(w http.ResponseWriter, r *http.Request) {
for cmd := range p.ch {
cmd.fn(w, r)
cmd.done <- true
}
}
func (p *handlerPuppet) done() { close(p.ch) }
func (p *handlerPuppet) do(fn func(http.ResponseWriter, *http.Request)) {
done := make(chan bool)
p.ch <- puppetCommand{fn, done}
<-done
}
func dockerLogs(container string) ([]byte, error) {
out, err := exec.Command("docker", "wait", container).CombinedOutput()
if err != nil {
return out, err
}
exitStatus, err := strconv.Atoi(strings.TrimSpace(string(out)))
if err != nil {
return out, errors.New("unexpected exit status from docker wait")
}
out, err = exec.Command("docker", "logs", container).CombinedOutput()
exec.Command("docker", "rm", container).Run()
if err == nil && exitStatus != 0 {
err = fmt.Errorf("exit status %d: %s", exitStatus, out)
}
return out, err
}
func kill(container string) {
exec.Command("docker", "kill", container).Run()
exec.Command("docker", "rm", container).Run()
}
func cleanDate(res *http.Response) {
if d := res.Header["Date"]; len(d) == 1 {
d[0] = "XXX"
}
}
func TestSorterPoolAllocs(t *testing.T) {
ss := []string{"a", "b", "c"}
h := http.Header{
"a": nil,
"b": nil,
"c": nil,
}
sorter := new(sorter)
if allocs := testing.AllocsPerRun(100, func() {
sorter.SortStrings(ss)
}); allocs >= 1 {
t.Logf("SortStrings allocs = %v; want <1", allocs)
}
if allocs := testing.AllocsPerRun(5, func() {
if len(sorter.Keys(h)) != 3 {
t.Fatal("wrong result")
}
}); allocs > 0 {
t.Logf("Keys allocs = %v; want <1", allocs)
}
}

View file

@ -1,130 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"bytes"
"errors"
"io"
"io/ioutil"
"testing"
)
func TestPipeClose(t *testing.T) {
var p pipe
p.b = new(bytes.Buffer)
a := errors.New("a")
b := errors.New("b")
p.CloseWithError(a)
p.CloseWithError(b)
_, err := p.Read(make([]byte, 1))
if err != a {
t.Errorf("err = %v want %v", err, a)
}
}
func TestPipeDoneChan(t *testing.T) {
var p pipe
done := p.Done()
select {
case <-done:
t.Fatal("done too soon")
default:
}
p.CloseWithError(io.EOF)
select {
case <-done:
default:
t.Fatal("should be done")
}
}
func TestPipeDoneChan_ErrFirst(t *testing.T) {
var p pipe
p.CloseWithError(io.EOF)
done := p.Done()
select {
case <-done:
default:
t.Fatal("should be done")
}
}
func TestPipeDoneChan_Break(t *testing.T) {
var p pipe
done := p.Done()
select {
case <-done:
t.Fatal("done too soon")
default:
}
p.BreakWithError(io.EOF)
select {
case <-done:
default:
t.Fatal("should be done")
}
}
func TestPipeDoneChan_Break_ErrFirst(t *testing.T) {
var p pipe
p.BreakWithError(io.EOF)
done := p.Done()
select {
case <-done:
default:
t.Fatal("should be done")
}
}
func TestPipeCloseWithError(t *testing.T) {
p := &pipe{b: new(bytes.Buffer)}
const body = "foo"
io.WriteString(p, body)
a := errors.New("test error")
p.CloseWithError(a)
all, err := ioutil.ReadAll(p)
if string(all) != body {
t.Errorf("read bytes = %q; want %q", all, body)
}
if err != a {
t.Logf("read error = %v, %v", err, a)
}
// Read and Write should fail.
if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 {
t.Errorf("Write(abc) after close\ngot %v, %v\nwant 0, %v", n, err, errClosedPipeWrite)
}
if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 {
t.Errorf("Read() after close\ngot %v, nil\nwant 0, %v", n, errClosedPipeWrite)
}
}
func TestPipeBreakWithError(t *testing.T) {
p := &pipe{b: new(bytes.Buffer)}
io.WriteString(p, "foo")
a := errors.New("test err")
p.BreakWithError(a)
all, err := ioutil.ReadAll(p)
if string(all) != "" {
t.Errorf("read bytes = %q; want empty string", all)
}
if err != a {
t.Logf("read error = %v, %v", err, a)
}
if p.b != nil {
t.Errorf("buffer should be nil after BreakWithError")
}
// Write should succeed silently.
if n, err := p.Write([]byte("abc")); err != nil || n != 3 {
t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, nil", n, err)
}
if p.b != nil {
t.Errorf("buffer should be nil after Write")
}
// Read should fail.
if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 {
t.Errorf("Read() after close\ngot %v, nil\nwant 0, not nil", n)
}
}

View file

@ -46,6 +46,7 @@ import (
"sync"
"time"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
)
@ -406,7 +407,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
// addresses during development.
//
// TODO: optionally enforce? Or enforce at the time we receive
// a new request, and verify the the ServerName matches the :authority?
// a new request, and verify the ServerName matches the :authority?
// But that precludes proxy situations, perhaps.
//
// So for now, do nothing here again.
@ -652,7 +653,7 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
if err == nil {
return
}
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) {
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) || err == errPrefaceTimeout {
// Boring, expected errors.
sc.vlogf(format, args...)
} else {
@ -897,8 +898,11 @@ func (sc *serverConn) sendServeMsg(msg interface{}) {
}
}
// readPreface reads the ClientPreface greeting from the peer
// or returns an error on timeout or an invalid greeting.
var errPrefaceTimeout = errors.New("timeout waiting for client preface")
// 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 {
errc := make(chan error, 1)
go func() {
@ -916,7 +920,7 @@ func (sc *serverConn) readPreface() error {
defer timer.Stop()
select {
case <-timer.C:
return errors.New("timeout waiting for client preface")
return errPrefaceTimeout
case err := <-errc:
if err == nil {
if VerboseLogs {
@ -1604,7 +1608,10 @@ func (sc *serverConn) processData(f *DataFrame) error {
// Sender sending more than they'd declared?
if st.declBodyBytes != -1 && st.bodyBytes+int64(len(data)) > st.declBodyBytes {
st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes))
return streamError(id, ErrCodeStreamClosed)
// RFC 7540, sec 8.1.2.6: A request or response is also malformed if the
// value of a content-length header field does not equal the sum of the
// DATA frame payload lengths that form the body.
return streamError(id, ErrCodeProtocol)
}
if f.Length > 0 {
// Check whether the client has flow control quota.
@ -1814,7 +1821,7 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
if st.trailer != nil {
for _, hf := range f.RegularFields() {
key := sc.canonicalHeader(hf.Name)
if !ValidTrailerHeader(key) {
if !httpguts.ValidTrailerHeader(key) {
// TODO: send more details to the peer somehow. But http2 has
// no way to send debug data at a stream level. Discuss with
// HTTP folk.
@ -2281,8 +2288,8 @@ func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) !=
// written in the trailers at the end of the response.
func (rws *responseWriterState) declareTrailer(k string) {
k = http.CanonicalHeaderKey(k)
if !ValidTrailerHeader(k) {
// Forbidden by RFC 2616 14.40.
if !httpguts.ValidTrailerHeader(k) {
// Forbidden by RFC 7230, section 4.1.2.
rws.conn.logf("ignoring invalid trailer %q", k)
return
}
@ -2305,6 +2312,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
isHeadResp := rws.req.Method == "HEAD"
if !rws.sentHeader {
rws.sentHeader = true
var ctype, clen string
if clen = rws.snapHeader.Get("Content-Length"); clen != "" {
rws.snapHeader.Del("Content-Length")
@ -2318,10 +2326,33 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
if clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) {
clen = strconv.Itoa(len(p))
}
_, hasContentType := rws.snapHeader["Content-Type"]
if !hasContentType && bodyAllowedForStatus(rws.status) {
ctype = http.DetectContentType(p)
if !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 {
if cto := rws.snapHeader.Get("X-Content-Type-Options"); strings.EqualFold("nosniff", cto) {
// nosniff is an explicit directive not to guess a content-type.
// Content-sniffing is no less susceptible to polyglot attacks via
// hosted content when done on the server.
ctype = "application/octet-stream"
rws.conn.logf("http2: WriteHeader called with X-Content-Type-Options:nosniff but no Content-Type")
} else {
ctype = http.DetectContentType(p)
}
}
var noSniff bool
if bodyAllowedForStatus(rws.status) && (rws.sentContentLen > 0 || len(p) > 0) {
// If the content type triggers client-side sniffing on old browsers,
// attach a X-Content-Type-Options header if not present (or explicitly nil).
if _, ok := rws.snapHeader["X-Content-Type-Options"]; !ok {
if hasContentType {
noSniff = httpguts.SniffedContentType(rws.snapHeader.Get("Content-Type"))
} else if ctype != "" {
noSniff = httpguts.SniffedContentType(ctype)
}
}
}
var date string
if _, ok := rws.snapHeader["Date"]; !ok {
// TODO(bradfitz): be faster here, like net/http? measure.
@ -2340,6 +2371,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
endStream: endStream,
contentType: ctype,
contentLength: clen,
noSniff: noSniff,
date: date,
})
if err != nil {
@ -2403,7 +2435,7 @@ const TrailerPrefix = "Trailer:"
// after the header has already been flushed. Because the Go
// ResponseWriter interface has no way to set Trailers (only the
// Header), and because we didn't want to expand the ResponseWriter
// interface, and because nobody used trailers, and because RFC 2616
// interface, and because nobody used trailers, and because RFC 7230
// says you SHOULD (but not must) predeclare any trailers in the
// header, the official ResponseWriter rules said trailers in Go must
// be predeclared, and then we reuse the same ResponseWriter.Header()
@ -2487,6 +2519,24 @@ func (w *responseWriter) Header() http.Header {
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) {
rws := w.rws
if rws == nil {
@ -2497,6 +2547,7 @@ func (w *responseWriter) WriteHeader(code int) {
func (rws *responseWriterState) writeHeader(code int) {
if !rws.wroteHeader {
checkWriteHeaderCode(code)
rws.wroteHeader = true
rws.status = code
if len(rws.handlerHeader) > 0 {
@ -2768,7 +2819,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
}
// foreachHeaderElement splits v according to the "#rule" construction
// in RFC 2616 section 2.1 and calls fn for each non-empty element.
// in RFC 7230 section 7 and calls fn for each non-empty element.
func foreachHeaderElement(v string, fn func(string)) {
v = textproto.TrimString(v)
if v == "" {
@ -2816,41 +2867,6 @@ func new400Handler(err error) http.HandlerFunc {
}
}
// ValidTrailerHeader reports whether name is a valid header field name to appear
// in trailers.
// See: http://tools.ietf.org/html/rfc7230#section-4.1.2
func ValidTrailerHeader(name string) bool {
name = http.CanonicalHeaderKey(name)
if strings.HasPrefix(name, "If-") || badTrailer[name] {
return false
}
return true
}
var badTrailer = map[string]bool{
"Authorization": true,
"Cache-Control": true,
"Connection": true,
"Content-Encoding": true,
"Content-Length": true,
"Content-Range": true,
"Content-Type": true,
"Expect": true,
"Host": true,
"Keep-Alive": true,
"Max-Forwards": true,
"Pragma": true,
"Proxy-Authenticate": true,
"Proxy-Authorization": true,
"Proxy-Connection": true,
"Range": true,
"Realm": true,
"Te": true,
"Trailer": true,
"Transfer-Encoding": true,
"Www-Authenticate": true,
}
// h1ServerKeepAlivesDisabled reports whether hs has its keep-alives
// disabled. See comments on h1ServerShutdownChan above for why
// the code is written this way.

View file

@ -1,521 +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 (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"sync"
"testing"
"time"
)
func TestServer_Push_Success(t *testing.T) {
const (
mainBody = "<html>index page</html>"
pushedBody = "<html>pushed page</html>"
userAgent = "testagent"
cookie = "testcookie"
)
var stURL string
checkPromisedReq := func(r *http.Request, wantMethod string, wantH http.Header) error {
if got, want := r.Method, wantMethod; got != want {
return fmt.Errorf("promised Req.Method=%q, want %q", got, want)
}
if got, want := r.Header, wantH; !reflect.DeepEqual(got, want) {
return fmt.Errorf("promised Req.Header=%q, want %q", got, want)
}
if got, want := "https://"+r.Host, stURL; got != want {
return fmt.Errorf("promised Req.Host=%q, want %q", got, want)
}
if r.Body == nil {
return fmt.Errorf("nil Body")
}
if buf, err := ioutil.ReadAll(r.Body); err != nil || len(buf) != 0 {
return fmt.Errorf("ReadAll(Body)=%q,%v, want '',nil", buf, err)
}
return nil
}
errc := make(chan error, 3)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
switch r.URL.RequestURI() {
case "/":
// Push "/pushed?get" as a GET request, using an absolute URL.
opt := &http.PushOptions{
Header: http.Header{
"User-Agent": {userAgent},
},
}
if err := w.(http.Pusher).Push(stURL+"/pushed?get", opt); err != nil {
errc <- fmt.Errorf("error pushing /pushed?get: %v", err)
return
}
// Push "/pushed?head" as a HEAD request, using a path.
opt = &http.PushOptions{
Method: "HEAD",
Header: http.Header{
"User-Agent": {userAgent},
"Cookie": {cookie},
},
}
if err := w.(http.Pusher).Push("/pushed?head", opt); err != nil {
errc <- fmt.Errorf("error pushing /pushed?head: %v", err)
return
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(mainBody)))
w.WriteHeader(200)
io.WriteString(w, mainBody)
errc <- nil
case "/pushed?get":
wantH := http.Header{}
wantH.Set("User-Agent", userAgent)
if err := checkPromisedReq(r, "GET", wantH); err != nil {
errc <- fmt.Errorf("/pushed?get: %v", err)
return
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(pushedBody)))
w.WriteHeader(200)
io.WriteString(w, pushedBody)
errc <- nil
case "/pushed?head":
wantH := http.Header{}
wantH.Set("User-Agent", userAgent)
wantH.Set("Cookie", cookie)
if err := checkPromisedReq(r, "HEAD", wantH); err != nil {
errc <- fmt.Errorf("/pushed?head: %v", err)
return
}
w.WriteHeader(204)
errc <- nil
default:
errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI())
}
})
stURL = st.ts.URL
// Send one request, which should push two responses.
st.greet()
getSlash(st)
for k := 0; k < 3; k++ {
select {
case <-time.After(2 * time.Second):
t.Errorf("timeout waiting for handler %d to finish", k)
case err := <-errc:
if err != nil {
t.Fatal(err)
}
}
}
checkPushPromise := func(f Frame, promiseID uint32, wantH [][2]string) error {
pp, ok := f.(*PushPromiseFrame)
if !ok {
return fmt.Errorf("got a %T; want *PushPromiseFrame", f)
}
if !pp.HeadersEnded() {
return fmt.Errorf("want END_HEADERS flag in PushPromiseFrame")
}
if got, want := pp.PromiseID, promiseID; got != want {
return fmt.Errorf("got PromiseID %v; want %v", got, want)
}
gotH := st.decodeHeader(pp.HeaderBlockFragment())
if !reflect.DeepEqual(gotH, wantH) {
return fmt.Errorf("got promised headers %v; want %v", gotH, wantH)
}
return nil
}
checkHeaders := func(f Frame, wantH [][2]string) error {
hf, ok := f.(*HeadersFrame)
if !ok {
return fmt.Errorf("got a %T; want *HeadersFrame", f)
}
gotH := st.decodeHeader(hf.HeaderBlockFragment())
if !reflect.DeepEqual(gotH, wantH) {
return fmt.Errorf("got response headers %v; want %v", gotH, wantH)
}
return nil
}
checkData := func(f Frame, wantData string) error {
df, ok := f.(*DataFrame)
if !ok {
return fmt.Errorf("got a %T; want *DataFrame", f)
}
if gotData := string(df.Data()); gotData != wantData {
return fmt.Errorf("got response data %q; want %q", gotData, wantData)
}
return nil
}
// Stream 1 has 2 PUSH_PROMISE + HEADERS + DATA
// Stream 2 has HEADERS + DATA
// Stream 4 has HEADERS
expected := map[uint32][]func(Frame) error{
1: {
func(f Frame) error {
return checkPushPromise(f, 2, [][2]string{
{":method", "GET"},
{":scheme", "https"},
{":authority", st.ts.Listener.Addr().String()},
{":path", "/pushed?get"},
{"user-agent", userAgent},
})
},
func(f Frame) error {
return checkPushPromise(f, 4, [][2]string{
{":method", "HEAD"},
{":scheme", "https"},
{":authority", st.ts.Listener.Addr().String()},
{":path", "/pushed?head"},
{"cookie", cookie},
{"user-agent", userAgent},
})
},
func(f Frame) error {
return checkHeaders(f, [][2]string{
{":status", "200"},
{"content-type", "text/html"},
{"content-length", strconv.Itoa(len(mainBody))},
})
},
func(f Frame) error {
return checkData(f, mainBody)
},
},
2: {
func(f Frame) error {
return checkHeaders(f, [][2]string{
{":status", "200"},
{"content-type", "text/html"},
{"content-length", strconv.Itoa(len(pushedBody))},
})
},
func(f Frame) error {
return checkData(f, pushedBody)
},
},
4: {
func(f Frame) error {
return checkHeaders(f, [][2]string{
{":status", "204"},
})
},
},
}
consumed := map[uint32]int{}
for k := 0; len(expected) > 0; k++ {
f, err := st.readFrame()
if err != nil {
for id, left := range expected {
t.Errorf("stream %d: missing %d frames", id, len(left))
}
t.Fatalf("readFrame %d: %v", k, err)
}
id := f.Header().StreamID
label := fmt.Sprintf("stream %d, frame %d", id, consumed[id])
if len(expected[id]) == 0 {
t.Fatalf("%s: unexpected frame %#+v", label, f)
}
check := expected[id][0]
expected[id] = expected[id][1:]
if len(expected[id]) == 0 {
delete(expected, id)
}
if err := check(f); err != nil {
t.Fatalf("%s: %v", label, err)
}
consumed[id]++
}
}
func TestServer_Push_SuccessNoRace(t *testing.T) {
// Regression test for issue #18326. Ensure the request handler can mutate
// pushed request headers without racing with the PUSH_PROMISE write.
errc := make(chan error, 2)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
switch r.URL.RequestURI() {
case "/":
opt := &http.PushOptions{
Header: http.Header{"User-Agent": {"testagent"}},
}
if err := w.(http.Pusher).Push("/pushed", opt); err != nil {
errc <- fmt.Errorf("error pushing: %v", err)
return
}
w.WriteHeader(200)
errc <- nil
case "/pushed":
// Update request header, ensure there is no race.
r.Header.Set("User-Agent", "newagent")
r.Header.Set("Cookie", "cookie")
w.WriteHeader(200)
errc <- nil
default:
errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI())
}
})
// Send one request, which should push one response.
st.greet()
getSlash(st)
for k := 0; k < 2; k++ {
select {
case <-time.After(2 * time.Second):
t.Errorf("timeout waiting for handler %d to finish", k)
case err := <-errc:
if err != nil {
t.Fatal(err)
}
}
}
}
func TestServer_Push_RejectRecursivePush(t *testing.T) {
// Expect two requests, but might get three if there's a bug and the second push succeeds.
errc := make(chan error, 3)
handler := func(w http.ResponseWriter, r *http.Request) error {
baseURL := "https://" + r.Host
switch r.URL.Path {
case "/":
if err := w.(http.Pusher).Push(baseURL+"/push1", nil); err != nil {
return fmt.Errorf("first Push()=%v, want nil", err)
}
return nil
case "/push1":
if got, want := w.(http.Pusher).Push(baseURL+"/push2", nil), ErrRecursivePush; got != want {
return fmt.Errorf("Push()=%v, want %v", got, want)
}
return nil
default:
return fmt.Errorf("unexpected path: %q", r.URL.Path)
}
}
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
errc <- handler(w, r)
})
defer st.Close()
st.greet()
getSlash(st)
if err := <-errc; err != nil {
t.Errorf("First request failed: %v", err)
}
if err := <-errc; err != nil {
t.Errorf("Second request failed: %v", err)
}
}
func testServer_Push_RejectSingleRequest(t *testing.T, doPush func(http.Pusher, *http.Request) error, settings ...Setting) {
// Expect one request, but might get two if there's a bug and the push succeeds.
errc := make(chan error, 2)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
errc <- doPush(w.(http.Pusher), r)
})
defer st.Close()
st.greet()
if err := st.fr.WriteSettings(settings...); err != nil {
st.t.Fatalf("WriteSettings: %v", err)
}
st.wantSettingsAck()
getSlash(st)
if err := <-errc; err != nil {
t.Error(err)
}
// Should not get a PUSH_PROMISE frame.
hf := st.wantHeaders()
if !hf.StreamEnded() {
t.Error("stream should end after headers")
}
}
func TestServer_Push_RejectIfDisabled(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if got, want := p.Push("https://"+r.Host+"/pushed", nil), http.ErrNotSupported; got != want {
return fmt.Errorf("Push()=%v, want %v", got, want)
}
return nil
},
Setting{SettingEnablePush, 0})
}
func TestServer_Push_RejectWhenNoConcurrentStreams(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if got, want := p.Push("https://"+r.Host+"/pushed", nil), ErrPushLimitReached; got != want {
return fmt.Errorf("Push()=%v, want %v", got, want)
}
return nil
},
Setting{SettingMaxConcurrentStreams, 0})
}
func TestServer_Push_RejectWrongScheme(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if err := p.Push("http://"+r.Host+"/pushed", nil); err == nil {
return errors.New("Push() should have failed (push target URL is http)")
}
return nil
})
}
func TestServer_Push_RejectMissingHost(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if err := p.Push("https:pushed", nil); err == nil {
return errors.New("Push() should have failed (push target URL missing host)")
}
return nil
})
}
func TestServer_Push_RejectRelativePath(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if err := p.Push("../test", nil); err == nil {
return errors.New("Push() should have failed (push target is a relative path)")
}
return nil
})
}
func TestServer_Push_RejectForbiddenMethod(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
if err := p.Push("https://"+r.Host+"/pushed", &http.PushOptions{Method: "POST"}); err == nil {
return errors.New("Push() should have failed (cannot promise a POST)")
}
return nil
})
}
func TestServer_Push_RejectForbiddenHeader(t *testing.T) {
testServer_Push_RejectSingleRequest(t,
func(p http.Pusher, r *http.Request) error {
header := http.Header{
"Content-Length": {"10"},
"Content-Encoding": {"gzip"},
"Trailer": {"Foo"},
"Te": {"trailers"},
"Host": {"test.com"},
":authority": {"test.com"},
}
if err := p.Push("https://"+r.Host+"/pushed", &http.PushOptions{Header: header}); err == nil {
return errors.New("Push() should have failed (forbidden headers)")
}
return nil
})
}
func TestServer_Push_StateTransitions(t *testing.T) {
const body = "foo"
gotPromise := make(chan bool)
finishedPush := make(chan bool)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
switch r.URL.RequestURI() {
case "/":
if err := w.(http.Pusher).Push("/pushed", nil); err != nil {
t.Errorf("Push error: %v", err)
}
// Don't finish this request until the push finishes so we don't
// nondeterministically interleave output frames with the push.
<-finishedPush
case "/pushed":
<-gotPromise
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.WriteHeader(200)
io.WriteString(w, body)
})
defer st.Close()
st.greet()
if st.stream(2) != nil {
t.Fatal("stream 2 should be empty")
}
if got, want := st.streamState(2), stateIdle; got != want {
t.Fatalf("streamState(2)=%v, want %v", got, want)
}
getSlash(st)
// After the PUSH_PROMISE is sent, the stream should be stateHalfClosedRemote.
st.wantPushPromise()
if got, want := st.streamState(2), stateHalfClosedRemote; got != want {
t.Fatalf("streamState(2)=%v, want %v", got, want)
}
// We stall the HTTP handler for "/pushed" until the above check. If we don't
// stall the handler, then the handler might write HEADERS and DATA and finish
// the stream before we check st.streamState(2) -- should that happen, we'll
// see stateClosed and fail the above check.
close(gotPromise)
st.wantHeaders()
if df := st.wantData(); !df.StreamEnded() {
t.Fatal("expected END_STREAM flag on DATA")
}
if got, want := st.streamState(2), stateClosed; got != want {
t.Fatalf("streamState(2)=%v, want %v", got, want)
}
close(finishedPush)
}
func TestServer_Push_RejectAfterGoAway(t *testing.T) {
var readyOnce sync.Once
ready := make(chan struct{})
errc := make(chan error, 2)
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
select {
case <-ready:
case <-time.After(5 * time.Second):
errc <- fmt.Errorf("timeout waiting for GOAWAY to be processed")
}
if got, want := w.(http.Pusher).Push("https://"+r.Host+"/pushed", nil), http.ErrNotSupported; got != want {
errc <- fmt.Errorf("Push()=%v, want %v", got, want)
}
errc <- nil
})
defer st.Close()
st.greet()
getSlash(st)
// Send GOAWAY and wait for it to be processed.
st.fr.WriteGoAway(1, ErrCodeNo, nil)
go func() {
for {
select {
case <-ready:
return
default:
}
st.sc.serveMsgCh <- func(loopNum int) {
if !st.sc.pushEnabled {
readyOnce.Do(func() { close(ready) })
}
}
}
}()
if err := <-errc; err != nil {
t.Error(err)
}
}

File diff suppressed because it is too large Load diff

View file

@ -27,9 +27,9 @@ import (
"sync"
"time"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/idna"
"golang.org/x/net/lex/httplex"
)
const (
@ -306,7 +306,26 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
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.
type RoundTripOpt struct {
@ -548,6 +567,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
// henc in response to SETTINGS frames?
cc.henc = hpack.NewEncoder(&cc.hbuf)
if t.AllowHTTP {
cc.nextStreamID = 3
}
if cs, ok := c.(connectionStater); ok {
state := cs.ConnectionState()
cc.tlsState = &state
@ -811,7 +834,7 @@ func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAf
cc.wmu.Lock()
endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
cc.wmu.Unlock()
traceWroteHeaders(cs.trace)
cc.mu.Unlock()
@ -932,6 +955,9 @@ func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
for {
cc.lastActive = time.Now()
if cc.closed || !cc.canTakeNewRequestLocked() {
if waitingForConn != nil {
close(waitingForConn)
}
return errClientConnUnusable
}
if int64(len(cc.streams))+1 <= int64(cc.maxConcurrentStreams) {
@ -964,13 +990,12 @@ func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
}
// 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)
frameSize := int(cc.maxFrameSize)
for len(hdrs) > 0 && cc.werr == nil {
chunk := hdrs
if len(chunk) > frameSize {
chunk = chunk[:frameSize]
if len(chunk) > maxFrameSize {
chunk = chunk[:maxFrameSize]
}
hdrs = hdrs[len(chunk):]
endHeaders := len(hdrs) == 0
@ -1087,13 +1112,17 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
}
}
cc.mu.Lock()
maxFrameSize := int(cc.maxFrameSize)
cc.mu.Unlock()
cc.wmu.Lock()
defer cc.wmu.Unlock()
// Two ways to send END_STREAM: either with trailers, or
// with an empty DATA frame.
if len(trls) > 0 {
err = cc.writeHeaders(cs.ID, true, trls)
err = cc.writeHeaders(cs.ID, true, maxFrameSize, trls)
} else {
err = cc.fr.WriteData(cs.ID, true, nil)
}
@ -1152,7 +1181,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
if host == "" {
host = req.URL.Host
}
host, err := httplex.PunycodeHostPort(host)
host, err := httpguts.PunycodeHostPort(host)
if err != nil {
return nil, err
}
@ -1177,11 +1206,11 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
// potentially pollute our hpack state. (We want to be able to
// continue to reuse the hpack encoder for future requests)
for k, vv := range req.Header {
if !httplex.ValidHeaderFieldName(k) {
if !httpguts.ValidHeaderFieldName(k) {
return nil, fmt.Errorf("invalid HTTP header name %q", k)
}
for _, v := range vv {
if !httplex.ValidHeaderFieldValue(v) {
if !httpguts.ValidHeaderFieldValue(v) {
return nil, fmt.Errorf("invalid HTTP header value %q for header %q", v, k)
}
}
@ -1373,17 +1402,12 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
type clientConnReadLoop struct {
cc *ClientConn
activeRes map[uint32]*clientStream // keyed by streamID
closeWhenIdle bool
}
// readLoop runs in its own goroutine and reads and dispatches frames.
func (cc *ClientConn) readLoop() {
rl := &clientConnReadLoop{
cc: cc,
activeRes: make(map[uint32]*clientStream),
}
rl := &clientConnReadLoop{cc: cc}
defer rl.cleanup()
cc.readerErr = rl.run()
if ce, ok := cc.readerErr.(ConnectionError); ok {
@ -1438,10 +1462,8 @@ func (rl *clientConnReadLoop) cleanup() {
} else if err == io.EOF {
err = io.ErrUnexpectedEOF
}
for _, cs := range rl.activeRes {
cs.bufPipe.CloseWithError(err)
}
for _, cs := range cc.streams {
cs.bufPipe.CloseWithError(err) // no-op if already closed
select {
case cs.resc <- resAndError{err: err}:
default:
@ -1519,7 +1541,7 @@ func (rl *clientConnReadLoop) run() error {
}
return err
}
if rl.closeWhenIdle && gotReply && maybeIdle && len(rl.activeRes) == 0 {
if rl.closeWhenIdle && gotReply && maybeIdle {
cc.closeIfIdle()
}
}
@ -1527,6 +1549,13 @@ func (rl *clientConnReadLoop) run() error {
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
cc := rl.cc
cs := cc.streamByID(f.StreamID, false)
if cs == nil {
// We'd get here if we canceled a request while the
// server had its response still in flight. So if this
// was just something we canceled, ignore it.
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
@ -1535,14 +1564,15 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
// 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.
defer cc.streamByID(f.StreamID, true)
}
cs := cc.streamByID(f.StreamID, false)
if cs == nil {
// We'd get here if we canceled a request while the
// server had its response still in flight. So if this
// was just something we canceled, ignore it.
return nil
//
// 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.trace != nil {
@ -1567,6 +1597,7 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
}
// Any other error type is a stream error.
cs.cc.writeStreamReset(f.StreamID, ErrCodeProtocol, err)
cc.forgetStreamID(cs.ID)
cs.resc <- resAndError{err: err}
return nil // return nil from process* funcs to keep conn alive
}
@ -1574,9 +1605,6 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
// (nil, nil) special case. See handleResponse docs.
return nil
}
if res.Body != noBody {
rl.activeRes[cs.ID] = cs
}
cs.resTrailer = &res.Trailer
cs.resc <- resAndError{res: res}
return nil
@ -1596,11 +1624,11 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
status := f.PseudoValue("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)
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 {
@ -1915,7 +1943,6 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
rl.closeWhenIdle = true
}
cs.bufPipe.closeWithErrorAndCode(err, code)
delete(rl.activeRes, cs.ID)
select {
case cs.resc <- resAndError{err: err}:
@ -2042,7 +2069,6 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
cs.bufPipe.CloseWithError(err)
cs.cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
}
delete(rl.activeRes, cs.ID)
return nil
}
@ -2225,7 +2251,7 @@ func (t *Transport) getBodyWriterState(cs *clientStream, body io.Reader) (s body
}
s.delay = t.expectContinueTimeout()
if s.delay == 0 ||
!httplex.HeaderValuesContainsToken(
!httpguts.HeaderValuesContainsToken(
cs.req.Header["Expect"],
"100-continue") {
return
@ -2280,5 +2306,5 @@ func (s bodyWriterState) scheduleBodyWrite() {
// isConnectionCloseRequest reports whether req should use its own
// connection for a single request and then close the connection.
func isConnectionCloseRequest(req *http.Request) bool {
return req.Close || httplex.HeaderValuesContainsToken(req.Header["Connection"], "close")
return req.Close || httpguts.HeaderValuesContainsToken(req.Header["Connection"], "close")
}

File diff suppressed because it is too large Load diff

View file

@ -11,8 +11,8 @@ import (
"net/http"
"net/url"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/lex/httplex"
)
// writeFramer is implemented by any type that is used to write frames.
@ -186,6 +186,7 @@ type writeResHeaders struct {
date string
contentType string
contentLength string
noSniff bool
}
func encKV(enc *hpack.Encoder, k, v string) {
@ -222,6 +223,9 @@ func (w *writeResHeaders) writeFrame(ctx writeContext) error {
if w.contentLength != "" {
encKV(enc, "content-length", w.contentLength)
}
if w.noSniff {
encKV(enc, "x-content-type-options", "nosniff")
}
if w.date != "" {
encKV(enc, "date", w.date)
}
@ -350,7 +354,7 @@ func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
}
isTE := k == "transfer-encoding"
for _, v := range vv {
if !httplex.ValidHeaderFieldValue(v) {
if !httpguts.ValidHeaderFieldValue(v) {
// TODO: return an error? golang.org/issue/14048
// For now just omit it.
continue

View file

@ -1,541 +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.
package http2
import (
"bytes"
"fmt"
"sort"
"testing"
)
func defaultPriorityWriteScheduler() *priorityWriteScheduler {
return NewPriorityWriteScheduler(nil).(*priorityWriteScheduler)
}
func checkPriorityWellFormed(ws *priorityWriteScheduler) error {
for id, n := range ws.nodes {
if id != n.id {
return fmt.Errorf("bad ws.nodes: ws.nodes[%d] = %d", id, n.id)
}
if n.parent == nil {
if n.next != nil || n.prev != nil {
return fmt.Errorf("bad node %d: nil parent but prev/next not nil", id)
}
continue
}
found := false
for k := n.parent.kids; k != nil; k = k.next {
if k.id == id {
found = true
break
}
}
if !found {
return fmt.Errorf("bad node %d: not found in parent %d kids list", id, n.parent.id)
}
}
return nil
}
func fmtTree(ws *priorityWriteScheduler, fmtNode func(*priorityNode) string) string {
var ids []int
for _, n := range ws.nodes {
ids = append(ids, int(n.id))
}
sort.Ints(ids)
var buf bytes.Buffer
for _, id := range ids {
if buf.Len() != 0 {
buf.WriteString(" ")
}
if id == 0 {
buf.WriteString(fmtNode(&ws.root))
} else {
buf.WriteString(fmtNode(ws.nodes[uint32(id)]))
}
}
return buf.String()
}
func fmtNodeParentSkipRoot(n *priorityNode) string {
switch {
case n.id == 0:
return ""
case n.parent == nil:
return fmt.Sprintf("%d{parent:nil}", n.id)
default:
return fmt.Sprintf("%d{parent:%d}", n.id, n.parent.id)
}
}
func fmtNodeWeightParentSkipRoot(n *priorityNode) string {
switch {
case n.id == 0:
return ""
case n.parent == nil:
return fmt.Sprintf("%d{weight:%d,parent:nil}", n.id, n.weight)
default:
return fmt.Sprintf("%d{weight:%d,parent:%d}", n.id, n.weight, n.parent.id)
}
}
func TestPriorityTwoStreams(t *testing.T) {
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{})
want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After open\ngot %q\nwant %q", got, want)
}
// Move 1's parent to 2.
ws.AdjustStream(1, PriorityParam{
StreamDep: 2,
Weight: 32,
Exclusive: false,
})
want = "1{weight:32,parent:2} 2{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityAdjustExclusiveZero(t *testing.T) {
// 1, 2, and 3 are all children of the 0 stream.
// Exclusive reprioritization to any of the streams should bring
// the rest of the streams under the reprioritized stream.
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{})
ws.OpenStream(3, OpenStreamOptions{})
want := "1{weight:15,parent:0} 2{weight:15,parent:0} 3{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After open\ngot %q\nwant %q", got, want)
}
ws.AdjustStream(2, PriorityParam{
StreamDep: 0,
Weight: 20,
Exclusive: true,
})
want = "1{weight:15,parent:2} 2{weight:20,parent:0} 3{weight:15,parent:2}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityAdjustOwnParent(t *testing.T) {
// Assigning a node as its own parent should have no effect.
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{})
ws.AdjustStream(2, PriorityParam{
StreamDep: 2,
Weight: 20,
Exclusive: true,
})
want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityClosedStreams(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxClosedNodesInTree: 2}).(*priorityWriteScheduler)
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
// Close the first three streams. We lose 1, but keep 2 and 3.
ws.CloseStream(1)
ws.CloseStream(2)
ws.CloseStream(3)
want := "2{weight:15,parent:0} 3{weight:15,parent:2} 4{weight:15,parent:3}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After close\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
// Adding a stream as an exclusive child of 1 gives it default
// priorities, since 1 is gone.
ws.OpenStream(5, OpenStreamOptions{})
ws.AdjustStream(5, PriorityParam{StreamDep: 1, Weight: 15, Exclusive: true})
// Adding a stream as an exclusive child of 2 should work, since 2 is not gone.
ws.OpenStream(6, OpenStreamOptions{})
ws.AdjustStream(6, PriorityParam{StreamDep: 2, Weight: 15, Exclusive: true})
want = "2{weight:15,parent:0} 3{weight:15,parent:6} 4{weight:15,parent:3} 5{weight:15,parent:0} 6{weight:15,parent:2}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After add streams\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityClosedStreamsDisabled(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteScheduler)
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
// Close the first two streams. We keep only 3.
ws.CloseStream(1)
ws.CloseStream(2)
want := "3{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After close\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityIdleStreams(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxIdleNodesInTree: 2}).(*priorityWriteScheduler)
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
ws.OpenStream(4, OpenStreamOptions{})
ws.OpenStream(5, OpenStreamOptions{})
ws.OpenStream(6, OpenStreamOptions{})
ws.AdjustStream(4, PriorityParam{StreamDep: 1, Weight: 15})
ws.AdjustStream(5, PriorityParam{StreamDep: 2, Weight: 15})
ws.AdjustStream(6, PriorityParam{StreamDep: 3, Weight: 15})
want := "2{weight:15,parent:0} 3{weight:20,parent:2} 4{weight:15,parent:0} 5{weight:15,parent:2} 6{weight:15,parent:3}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After open\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPriorityIdleStreamsDisabled(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteScheduler)
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
ws.OpenStream(4, OpenStreamOptions{})
want := "4{weight:15,parent:0}"
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
t.Errorf("After open\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPrioritySection531NonExclusive(t *testing.T) {
// Example from RFC 7540 Section 5.3.1.
// A,B,C,D = 1,2,3,4
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
ws.OpenStream(4, OpenStreamOptions{})
ws.AdjustStream(4, PriorityParam{
StreamDep: 1,
Weight: 15,
Exclusive: false,
})
want := "1{parent:0} 2{parent:1} 3{parent:1} 4{parent:1}"
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPrioritySection531Exclusive(t *testing.T) {
// Example from RFC 7540 Section 5.3.1.
// A,B,C,D = 1,2,3,4
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
ws.OpenStream(4, OpenStreamOptions{})
ws.AdjustStream(4, PriorityParam{
StreamDep: 1,
Weight: 15,
Exclusive: true,
})
want := "1{parent:0} 2{parent:4} 3{parent:4} 4{parent:1}"
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func makeSection533Tree() *priorityWriteScheduler {
// Initial tree from RFC 7540 Section 5.3.3.
// A,B,C,D,E,F = 1,2,3,4,5,6
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
return ws
}
func TestPrioritySection533NonExclusive(t *testing.T) {
// Example from RFC 7540 Section 5.3.3.
// A,B,C,D,E,F = 1,2,3,4,5,6
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
ws.AdjustStream(1, PriorityParam{
StreamDep: 4,
Weight: 15,
Exclusive: false,
})
want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:4}"
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func TestPrioritySection533Exclusive(t *testing.T) {
// Example from RFC 7540 Section 5.3.3.
// A,B,C,D,E,F = 1,2,3,4,5,6
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
ws.AdjustStream(1, PriorityParam{
StreamDep: 4,
Weight: 15,
Exclusive: true,
})
want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:1}"
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
}
if err := checkPriorityWellFormed(ws); err != nil {
t.Error(err)
}
}
func checkPopAll(ws WriteScheduler, order []uint32) error {
for k, id := range order {
wr, ok := ws.Pop()
if !ok {
return fmt.Errorf("Pop[%d]: got ok=false, want %d (order=%v)", k, id, order)
}
if got := wr.StreamID(); got != id {
return fmt.Errorf("Pop[%d]: got %v, want %d (order=%v)", k, got, id, order)
}
}
wr, ok := ws.Pop()
if ok {
return fmt.Errorf("Pop[%d]: got %v, want ok=false (order=%v)", len(order), wr.StreamID(), order)
}
return nil
}
func TestPriorityPopFrom533Tree(t *testing.T) {
ws := makeSection533Tree()
ws.Push(makeWriteHeadersRequest(3 /*C*/))
ws.Push(makeWriteNonStreamRequest())
ws.Push(makeWriteHeadersRequest(5 /*E*/))
ws.Push(makeWriteHeadersRequest(1 /*A*/))
t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
if err := checkPopAll(ws, []uint32{0 /*NonStream*/, 1, 3, 5}); err != nil {
t.Error(err)
}
}
func TestPriorityPopFromLinearTree(t *testing.T) {
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
ws.Push(makeWriteHeadersRequest(3))
ws.Push(makeWriteHeadersRequest(4))
ws.Push(makeWriteHeadersRequest(1))
ws.Push(makeWriteHeadersRequest(2))
ws.Push(makeWriteNonStreamRequest())
ws.Push(makeWriteNonStreamRequest())
t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
if err := checkPopAll(ws, []uint32{0, 0 /*NonStreams*/, 1, 2, 3, 4}); err != nil {
t.Error(err)
}
}
func TestPriorityFlowControl(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: false})
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
sc := &serverConn{maxFrameSize: 16}
st1 := &stream{id: 1, sc: sc}
st2 := &stream{id: 2, sc: sc}
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 16), false}, st1, nil})
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 16), false}, st2, nil})
ws.AdjustStream(2, PriorityParam{StreamDep: 1})
// No flow-control bytes available.
if wr, ok := ws.Pop(); ok {
t.Fatalf("Pop(limited by flow control)=%v,true, want false", wr)
}
// Add enough flow-control bytes to write st2 in two Pop calls.
// Should write data from st2 even though it's lower priority than st1.
for i := 1; i <= 2; i++ {
st2.flow.add(8)
wr, ok := ws.Pop()
if !ok {
t.Fatalf("Pop(%d)=false, want true", i)
}
if got, want := wr.DataSize(), 8; got != want {
t.Fatalf("Pop(%d)=%d bytes, want %d bytes", i, got, want)
}
}
}
func TestPriorityThrottleOutOfOrderWrites(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: true})
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
sc := &serverConn{maxFrameSize: 4096}
st1 := &stream{id: 1, sc: sc}
st2 := &stream{id: 2, sc: sc}
st1.flow.add(4096)
st2.flow.add(4096)
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 4096), false}, st2, nil})
ws.AdjustStream(2, PriorityParam{StreamDep: 1})
// We have enough flow-control bytes to write st2 in a single Pop call.
// However, due to out-of-order write throttling, the first call should
// only write 1KB.
wr, ok := ws.Pop()
if !ok {
t.Fatalf("Pop(st2.first)=false, want true")
}
if got, want := wr.StreamID(), uint32(2); got != want {
t.Fatalf("Pop(st2.first)=stream %d, want stream %d", got, want)
}
if got, want := wr.DataSize(), 1024; got != want {
t.Fatalf("Pop(st2.first)=%d bytes, want %d bytes", got, want)
}
// Now add data on st1. This should take precedence.
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 4096), false}, st1, nil})
wr, ok = ws.Pop()
if !ok {
t.Fatalf("Pop(st1)=false, want true")
}
if got, want := wr.StreamID(), uint32(1); got != want {
t.Fatalf("Pop(st1)=stream %d, want stream %d", got, want)
}
if got, want := wr.DataSize(), 4096; got != want {
t.Fatalf("Pop(st1)=%d bytes, want %d bytes", got, want)
}
// Should go back to writing 1KB from st2.
wr, ok = ws.Pop()
if !ok {
t.Fatalf("Pop(st2.last)=false, want true")
}
if got, want := wr.StreamID(), uint32(2); got != want {
t.Fatalf("Pop(st2.last)=stream %d, want stream %d", got, want)
}
if got, want := wr.DataSize(), 1024; got != want {
t.Fatalf("Pop(st2.last)=%d bytes, want %d bytes", got, want)
}
}
func TestPriorityWeights(t *testing.T) {
ws := defaultPriorityWriteScheduler()
ws.OpenStream(1, OpenStreamOptions{})
ws.OpenStream(2, OpenStreamOptions{})
sc := &serverConn{maxFrameSize: 8}
st1 := &stream{id: 1, sc: sc}
st2 := &stream{id: 2, sc: sc}
st1.flow.add(40)
st2.flow.add(40)
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 40), false}, st1, nil})
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 40), false}, st2, nil})
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 34})
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 9})
// st1 gets 3.5x the bandwidth of st2 (3.5 = (34+1)/(9+1)).
// The maximum frame size is 8 bytes. The write sequence should be:
// st1, total bytes so far is (st1=8, st=0)
// st2, total bytes so far is (st1=8, st=8)
// st1, total bytes so far is (st1=16, st=8)
// st1, total bytes so far is (st1=24, st=8) // 3x bandwidth
// st1, total bytes so far is (st1=32, st=8) // 4x bandwidth
// st2, total bytes so far is (st1=32, st=16) // 2x bandwidth
// st1, total bytes so far is (st1=40, st=16)
// st2, total bytes so far is (st1=40, st=24)
// st2, total bytes so far is (st1=40, st=32)
// st2, total bytes so far is (st1=40, st=40)
if err := checkPopAll(ws, []uint32{1, 2, 1, 1, 1, 2, 1, 2, 2, 2}); err != nil {
t.Error(err)
}
}
func TestPriorityRstStreamOnNonOpenStreams(t *testing.T) {
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{
MaxClosedNodesInTree: 0,
MaxIdleNodesInTree: 0,
})
ws.OpenStream(1, OpenStreamOptions{})
ws.CloseStream(1)
ws.Push(FrameWriteRequest{write: streamError(1, ErrCodeProtocol)})
ws.Push(FrameWriteRequest{write: streamError(2, ErrCodeProtocol)})
if err := checkPopAll(ws, []uint32{1, 2}); err != nil {
t.Error(err)
}
}

View file

@ -1,44 +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.
package http2
import "testing"
func TestRandomScheduler(t *testing.T) {
ws := NewRandomWriteScheduler()
ws.Push(makeWriteHeadersRequest(3))
ws.Push(makeWriteHeadersRequest(4))
ws.Push(makeWriteHeadersRequest(1))
ws.Push(makeWriteHeadersRequest(2))
ws.Push(makeWriteNonStreamRequest())
ws.Push(makeWriteNonStreamRequest())
// Pop all frames. Should get the non-stream requests first,
// followed by the stream requests in any order.
var order []FrameWriteRequest
for {
wr, ok := ws.Pop()
if !ok {
break
}
order = append(order, wr)
}
t.Logf("got frames: %v", order)
if len(order) != 6 {
t.Fatalf("got %d frames, expected 6", len(order))
}
if order[0].StreamID() != 0 || order[1].StreamID() != 0 {
t.Fatal("expected non-stream frames first", order[0], order[1])
}
got := make(map[uint32]bool)
for _, wr := range order[2:] {
got[wr.StreamID()] = true
}
for id := uint32(1); id <= 4; id++ {
if !got[id] {
t.Errorf("frame not found for stream %d", id)
}
}
}

View file

@ -1,125 +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.
package http2
import (
"fmt"
"math"
"reflect"
"testing"
)
func makeWriteNonStreamRequest() FrameWriteRequest {
return FrameWriteRequest{writeSettingsAck{}, nil, nil}
}
func makeWriteHeadersRequest(streamID uint32) FrameWriteRequest {
st := &stream{id: streamID}
return FrameWriteRequest{&writeResHeaders{streamID: streamID, httpResCode: 200}, st, nil}
}
func checkConsume(wr FrameWriteRequest, nbytes int32, want []FrameWriteRequest) error {
consumed, rest, n := wr.Consume(nbytes)
var wantConsumed, wantRest FrameWriteRequest
switch len(want) {
case 0:
case 1:
wantConsumed = want[0]
case 2:
wantConsumed = want[0]
wantRest = want[1]
}
if !reflect.DeepEqual(consumed, wantConsumed) || !reflect.DeepEqual(rest, wantRest) || n != len(want) {
return fmt.Errorf("got %v, %v, %v\nwant %v, %v, %v", consumed, rest, n, wantConsumed, wantRest, len(want))
}
return nil
}
func TestFrameWriteRequestNonData(t *testing.T) {
wr := makeWriteNonStreamRequest()
if got, want := wr.DataSize(), 0; got != want {
t.Errorf("DataSize: got %v, want %v", got, want)
}
// Non-DATA frames are always consumed whole.
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
t.Errorf("Consume:\n%v", err)
}
}
func TestFrameWriteRequestData(t *testing.T) {
st := &stream{
id: 1,
sc: &serverConn{maxFrameSize: 16},
}
const size = 32
wr := FrameWriteRequest{&writeData{st.id, make([]byte, size), true}, st, make(chan error)}
if got, want := wr.DataSize(), size; got != want {
t.Errorf("DataSize: got %v, want %v", got, want)
}
// No flow-control bytes available: cannot consume anything.
if err := checkConsume(wr, math.MaxInt32, []FrameWriteRequest{}); err != nil {
t.Errorf("Consume(limited by flow control):\n%v", err)
}
// Add enough flow-control bytes to consume the entire frame,
// but we're now restricted by st.sc.maxFrameSize.
st.flow.add(size)
want := []FrameWriteRequest{
{
write: &writeData{st.id, make([]byte, st.sc.maxFrameSize), false},
stream: st,
done: nil,
},
{
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize), true},
stream: st,
done: wr.done,
},
}
if err := checkConsume(wr, math.MaxInt32, want); err != nil {
t.Errorf("Consume(limited by maxFrameSize):\n%v", err)
}
rest := want[1]
// Consume 8 bytes from the remaining frame.
want = []FrameWriteRequest{
{
write: &writeData{st.id, make([]byte, 8), false},
stream: st,
done: nil,
},
{
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true},
stream: st,
done: wr.done,
},
}
if err := checkConsume(rest, 8, want); err != nil {
t.Errorf("Consume(8):\n%v", err)
}
rest = want[1]
// Consume all remaining bytes.
want = []FrameWriteRequest{
{
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true},
stream: st,
done: wr.done,
},
}
if err := checkConsume(rest, math.MaxInt32, want); err != nil {
t.Errorf("Consume(remainder):\n%v", err)
}
}
func TestFrameWriteRequest_StreamID(t *testing.T) {
const streamID = 123
wr := FrameWriteRequest{write: streamError(streamID, ErrCodeNo)}
if got := wr.StreamID(); got != streamID {
t.Errorf("FrameWriteRequest(StreamError) = %v; want %v", got, streamID)
}
}

View file

@ -1,356 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"bytes"
"encoding/xml"
"flag"
"fmt"
"io"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"testing"
)
var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")
// The global map of sentence coverage for the http2 spec.
var defaultSpecCoverage specCoverage
var loadSpecOnce sync.Once
func loadSpec() {
if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil {
panic(err)
} else {
defaultSpecCoverage = readSpecCov(f)
f.Close()
}
}
// covers marks all sentences for section sec in defaultSpecCoverage. Sentences not
// "covered" will be included in report outputted by TestSpecCoverage.
func covers(sec, sentences string) {
loadSpecOnce.Do(loadSpec)
defaultSpecCoverage.cover(sec, sentences)
}
type specPart struct {
section string
sentence string
}
func (ss specPart) Less(oo specPart) bool {
atoi := func(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
a := strings.Split(ss.section, ".")
b := strings.Split(oo.section, ".")
for len(a) > 0 {
if len(b) == 0 {
return false
}
x, y := atoi(a[0]), atoi(b[0])
if x == y {
a, b = a[1:], b[1:]
continue
}
return x < y
}
if len(b) > 0 {
return true
}
return false
}
type bySpecSection []specPart
func (a bySpecSection) Len() int { return len(a) }
func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) }
func (a bySpecSection) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type specCoverage struct {
coverage map[specPart]bool
d *xml.Decoder
}
func joinSection(sec []int) string {
s := fmt.Sprintf("%d", sec[0])
for _, n := range sec[1:] {
s = fmt.Sprintf("%s.%d", s, n)
}
return s
}
func (sc specCoverage) readSection(sec []int) {
var (
buf = new(bytes.Buffer)
sub = 0
)
for {
tk, err := sc.d.Token()
if err != nil {
if err == io.EOF {
return
}
panic(err)
}
switch v := tk.(type) {
case xml.StartElement:
if skipElement(v) {
if err := sc.d.Skip(); err != nil {
panic(err)
}
if v.Name.Local == "section" {
sub++
}
break
}
switch v.Name.Local {
case "section":
sub++
sc.readSection(append(sec, sub))
case "xref":
buf.Write(sc.readXRef(v))
}
case xml.CharData:
if len(sec) == 0 {
break
}
buf.Write(v)
case xml.EndElement:
if v.Name.Local == "section" {
sc.addSentences(joinSection(sec), buf.String())
return
}
}
}
}
func (sc specCoverage) readXRef(se xml.StartElement) []byte {
var b []byte
for {
tk, err := sc.d.Token()
if err != nil {
panic(err)
}
switch v := tk.(type) {
case xml.CharData:
if b != nil {
panic("unexpected CharData")
}
b = []byte(string(v))
case xml.EndElement:
if v.Name.Local != "xref" {
panic("expected </xref>")
}
if b != nil {
return b
}
sig := attrSig(se)
switch sig {
case "target":
return []byte(fmt.Sprintf("[%s]", attrValue(se, "target")))
case "fmt-of,rel,target", "fmt-,,rel,target":
return []byte(fmt.Sprintf("[%s, %s]", attrValue(se, "target"), attrValue(se, "rel")))
case "fmt-of,sec,target", "fmt-,,sec,target":
return []byte(fmt.Sprintf("[section %s of %s]", attrValue(se, "sec"), attrValue(se, "target")))
case "fmt-of,rel,sec,target":
return []byte(fmt.Sprintf("[section %s of %s, %s]", attrValue(se, "sec"), attrValue(se, "target"), attrValue(se, "rel")))
default:
panic(fmt.Sprintf("unknown attribute signature %q in %#v", sig, fmt.Sprintf("%#v", se)))
}
default:
panic(fmt.Sprintf("unexpected tag %q", v))
}
}
}
var skipAnchor = map[string]bool{
"intro": true,
"Overview": true,
}
var skipTitle = map[string]bool{
"Acknowledgements": true,
"Change Log": true,
"Document Organization": true,
"Conventions and Terminology": true,
}
func skipElement(s xml.StartElement) bool {
switch s.Name.Local {
case "artwork":
return true
case "section":
for _, attr := range s.Attr {
switch attr.Name.Local {
case "anchor":
if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") {
return true
}
case "title":
if skipTitle[attr.Value] {
return true
}
}
}
}
return false
}
func readSpecCov(r io.Reader) specCoverage {
sc := specCoverage{
coverage: map[specPart]bool{},
d: xml.NewDecoder(r)}
sc.readSection(nil)
return sc
}
func (sc specCoverage) addSentences(sec string, sentence string) {
for _, s := range parseSentences(sentence) {
sc.coverage[specPart{sec, s}] = false
}
}
func (sc specCoverage) cover(sec string, sentence string) {
for _, s := range parseSentences(sentence) {
p := specPart{sec, s}
if _, ok := sc.coverage[p]; !ok {
panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s))
}
sc.coverage[specPart{sec, s}] = true
}
}
var whitespaceRx = regexp.MustCompile(`\s+`)
func parseSentences(sens string) []string {
sens = strings.TrimSpace(sens)
if sens == "" {
return nil
}
ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ")
for i, s := range ss {
s = strings.TrimSpace(s)
if !strings.HasSuffix(s, ".") {
s += "."
}
ss[i] = s
}
return ss
}
func TestSpecParseSentences(t *testing.T) {
tests := []struct {
ss string
want []string
}{
{"Sentence 1. Sentence 2.",
[]string{
"Sentence 1.",
"Sentence 2.",
}},
{"Sentence 1. \nSentence 2.\tSentence 3.",
[]string{
"Sentence 1.",
"Sentence 2.",
"Sentence 3.",
}},
}
for i, tt := range tests {
got := parseSentences(tt.ss)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("%d: got = %q, want %q", i, got, tt.want)
}
}
}
func TestSpecCoverage(t *testing.T) {
if !*coverSpec {
t.Skip()
}
loadSpecOnce.Do(loadSpec)
var (
list []specPart
cv = defaultSpecCoverage.coverage
total = len(cv)
complete = 0
)
for sp, touched := range defaultSpecCoverage.coverage {
if touched {
complete++
} else {
list = append(list, sp)
}
}
sort.Stable(bySpecSection(list))
if testing.Short() && len(list) > 5 {
list = list[:5]
}
for _, p := range list {
t.Errorf("\tSECTION %s: %s", p.section, p.sentence)
}
t.Logf("%d/%d (%d%%) sentences covered", complete, total, (complete/total)*100)
}
func attrSig(se xml.StartElement) string {
var names []string
for _, attr := range se.Attr {
if attr.Name.Local == "fmt" {
names = append(names, "fmt-"+attr.Value)
} else {
names = append(names, attr.Name.Local)
}
}
sort.Strings(names)
return strings.Join(names, ",")
}
func attrValue(se xml.StartElement, attr string) string {
for _, a := range se.Attr {
if a.Name.Local == attr {
return a.Value
}
}
panic("unknown attribute " + attr)
}
func TestSpecPartLess(t *testing.T) {
tests := []struct {
sec1, sec2 string
want bool
}{
{"6.2.1", "6.2", false},
{"6.2", "6.2.1", true},
{"6.10", "6.10.1", true},
{"6.10", "6.1.1", false}, // 10, not 1
{"6.1", "6.1", false}, // equal, so not less
}
for _, tt := range tests {
got := (specPart{tt.sec1, "foo"}).Less(specPart{tt.sec2, "foo"})
if got != tt.want {
t.Errorf("Less(%q, %q) = %v; want %v", tt.sec1, tt.sec2, got, tt.want)
}
}
}

View file

@ -1,70 +0,0 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package idna_test
import (
"fmt"
"golang.org/x/net/idna"
)
func ExampleProfile() {
// Raw Punycode has no restrictions and does no mappings.
fmt.Println(idna.ToASCII(""))
fmt.Println(idna.ToASCII("*.faß.com"))
fmt.Println(idna.Punycode.ToASCII("*.faß.com"))
// Rewrite IDN for lookup. This (currently) uses transitional mappings to
// find a balance between IDNA2003 and IDNA2008 compatibility.
fmt.Println(idna.Lookup.ToASCII(""))
fmt.Println(idna.Lookup.ToASCII("www.faß.com"))
// Convert an IDN to ASCII for registration purposes. This changes the
// encoding, but reports an error if the input was illformed.
fmt.Println(idna.Registration.ToASCII(""))
fmt.Println(idna.Registration.ToASCII("www.faß.com"))
// Output:
// <nil>
// *.xn--fa-hia.com <nil>
// *.xn--fa-hia.com <nil>
// <nil>
// www.fass.com <nil>
// idna: invalid label ""
// www.xn--fa-hia.com <nil>
}
func ExampleNew() {
var p *idna.Profile
// Raw Punycode has no restrictions and does no mappings.
p = idna.New()
fmt.Println(p.ToASCII("*.faß.com"))
// Do mappings. Note that star is not allowed in a DNS lookup.
p = idna.New(
idna.MapForLookup(),
idna.Transitional(true)) // Map ß -> ss
fmt.Println(p.ToASCII("*.faß.com"))
// Lookup for registration. Also does not allow '*'.
p = idna.New(idna.ValidateForRegistration())
fmt.Println(p.ToUnicode("*.faß.com"))
// Set up a profile maps for lookup, but allows wild cards.
p = idna.New(
idna.MapForLookup(),
idna.Transitional(true), // Map ß -> ss
idna.StrictDomainName(false)) // Set more permissive ASCII rules.
fmt.Println(p.ToASCII("*.faß.com"))
// Output:
// *.xn--fa-hia.com <nil>
// *.fass.com idna: disallowed rune U+002A
// *.faß.com idna: disallowed rune U+002A
// *.fass.com <nil>
}

View file

@ -1,108 +0,0 @@
// Copyright 2012 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 idna
import (
"testing"
)
var idnaTestCases = [...]struct {
ascii, unicode string
}{
// Labels.
{"books", "books"},
{"xn--bcher-kva", "bücher"},
// Domains.
{"foo--xn--bar.org", "foo--xn--bar.org"},
{"golang.org", "golang.org"},
{"example.xn--p1ai", "example.рф"},
{"xn--czrw28b.tw", "商業.tw"},
{"www.xn--mller-kva.de", "www.müller.de"},
}
func TestIDNA(t *testing.T) {
for _, tc := range idnaTestCases {
if a, err := ToASCII(tc.unicode); err != nil {
t.Errorf("ToASCII(%q): %v", tc.unicode, err)
} else if a != tc.ascii {
t.Errorf("ToASCII(%q): got %q, want %q", tc.unicode, a, tc.ascii)
}
if u, err := ToUnicode(tc.ascii); err != nil {
t.Errorf("ToUnicode(%q): %v", tc.ascii, err)
} else if u != tc.unicode {
t.Errorf("ToUnicode(%q): got %q, want %q", tc.ascii, u, tc.unicode)
}
}
}
func TestIDNASeparators(t *testing.T) {
type subCase struct {
unicode string
wantASCII string
wantErr bool
}
testCases := []struct {
name string
profile *Profile
subCases []subCase
}{
{
name: "Punycode", profile: Punycode,
subCases: []subCase{
{"example\u3002jp", "xn--examplejp-ck3h", false},
{"東京\uFF0Ejp", "xn--jp-l92cn98g071o", false},
{"大阪\uFF61jp", "xn--jp-ku9cz72u463f", false},
},
},
{
name: "Lookup", profile: Lookup,
subCases: []subCase{
{"example\u3002jp", "example.jp", false},
{"東京\uFF0Ejp", "xn--1lqs71d.jp", false},
{"大阪\uFF61jp", "xn--pssu33l.jp", false},
},
},
{
name: "Display", profile: Display,
subCases: []subCase{
{"example\u3002jp", "example.jp", false},
{"東京\uFF0Ejp", "xn--1lqs71d.jp", false},
{"大阪\uFF61jp", "xn--pssu33l.jp", false},
},
},
{
name: "Registration", profile: Registration,
subCases: []subCase{
{"example\u3002jp", "", true},
{"東京\uFF0Ejp", "", true},
{"大阪\uFF61jp", "", true},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, c := range tc.subCases {
gotA, err := tc.profile.ToASCII(c.unicode)
if c.wantErr {
if err == nil {
t.Errorf("ToASCII(%q): got no error, but an error expected", c.unicode)
}
} else {
if err != nil {
t.Errorf("ToASCII(%q): got err=%v, but no error expected", c.unicode, err)
} else if gotA != c.wantASCII {
t.Errorf("ToASCII(%q): got %q, want %q", c.unicode, gotA, c.wantASCII)
}
}
}
})
}
}
// TODO(nigeltao): test errors, once we've specified when ToASCII and ToUnicode
// return errors.

Some files were not shown because too many files have changed in this diff Show more