Merge pull request #14747 from eparis/update-golang-x-net

Update golang.org/x/net to fix godep reproducability problem
This commit is contained in:
Fabio Yeon 2015-09-29 11:18:55 -07:00
commit 26c51881ca
27 changed files with 1413 additions and 796 deletions

6
Godeps/Godeps.json generated
View File

@ -578,15 +578,15 @@
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
"Rev": "c2528b2dd8352441850638a8bb678c2ad056fd3e"
},
{
"ImportPath": "golang.org/x/net/html",
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
"Rev": "c2528b2dd8352441850638a8bb678c2ad056fd3e"
},
{
"ImportPath": "golang.org/x/net/websocket",
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
"Rev": "c2528b2dd8352441850638a8bb678c2ad056fd3e"
},
{
"ImportPath": "golang.org/x/oauth2",

View File

@ -64,18 +64,21 @@ type Context interface {
//
// Done is provided for use in select statements:
//
// // DoSomething calls DoSomethingSlow and returns as soon as
// // it returns or ctx.Done is closed.
// func DoSomething(ctx context.Context) (Result, error) {
// c := make(chan Result, 1)
// go func() { c <- DoSomethingSlow(ctx) }()
// select {
// case res := <-c:
// return res, nil
// case <-ctx.Done():
// return nil, ctx.Err()
// }
// }
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out <-chan Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See http://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancelation.
@ -202,6 +205,9 @@ type CancelFunc func()
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
@ -262,6 +268,19 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) {
}
}
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
@ -316,13 +335,7 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
c.mu.Unlock()
if removeFromParent {
if p, ok := parentCancelCtx(c.Context); ok {
p.mu.Lock()
if p.children != nil {
delete(p.children, c)
}
p.mu.Unlock()
}
removeChild(c.Context, c)
}
}
@ -333,9 +346,8 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with the deadline
// timer, so code should call cancel as soon as the operations running in this
// Context complete.
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
@ -380,7 +392,11 @@ func (c *timerCtx) String() string {
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(removeFromParent, err)
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
@ -391,9 +407,8 @@ func (c *timerCtx) cancel(removeFromParent bool, err error) {
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with the deadline
// timer, so code should call cancel as soon as the operations running in this
// Context complete:
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)

View File

@ -375,7 +375,7 @@ func TestAllocs(t *testing.T) {
<-c.Done()
},
limit: 8,
gccgoLimit: 13,
gccgoLimit: 15,
},
{
desc: "WithCancel(bg)",
@ -551,3 +551,25 @@ func testLayers(t *testing.T, seed int64, testTimeout bool) {
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

@ -0,0 +1,18 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.5
package ctxhttp
import "net/http"
func canceler(client *http.Client, req *http.Request) func() {
ch := make(chan struct{})
req.Cancel = ch
return func() {
close(ch)
}
}

View File

@ -0,0 +1,23 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.5
package ctxhttp
import "net/http"
type requestCanceler interface {
CancelRequest(*http.Request)
}
func canceler(client *http.Client, req *http.Request) func() {
rc, ok := client.Transport.(requestCanceler)
if !ok {
return func() {}
}
return func() {
rc.CancelRequest(req)
}
}

View File

@ -0,0 +1,79 @@
// 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.
// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
package ctxhttp
import (
"io"
"net/http"
"net/url"
"strings"
"golang.org/x/net/context"
)
// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
// If the client is nil, http.DefaultClient is used.
// If the context is canceled or times out, ctx.Err() will be returned.
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
// Request cancelation changed in Go 1.5, see cancelreq.go and cancelreq_go14.go.
cancel := canceler(client, req)
type responseAndError struct {
resp *http.Response
err error
}
result := make(chan responseAndError, 1)
go func() {
resp, err := client.Do(req)
result <- responseAndError{resp, err}
}()
select {
case <-ctx.Done():
cancel()
return nil, ctx.Err()
case r := <-result:
return r.resp, r.err
}
}
// Get issues a GET request via the Do function.
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
}
// Head issues a HEAD request via the Do function.
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
}
// Post issues a POST request via the Do function.
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return Do(ctx, client, req)
}
// PostForm issues a POST request via the Do function.
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}

View File

@ -0,0 +1,72 @@
// 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.
package ctxhttp
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"golang.org/x/net/context"
)
const (
requestDuration = 100 * time.Millisecond
requestBody = "ok"
)
func TestNoTimeout(t *testing.T) {
ctx := context.Background()
resp, err := doRequest(ctx)
if resp == nil || err != nil {
t.Fatalf("error received from client: %v %v", err, resp)
}
}
func TestCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(requestDuration / 2)
cancel()
}()
resp, err := doRequest(ctx)
if resp != nil || err == nil {
t.Fatalf("expected error, didn't get one. resp: %v", resp)
}
if err != ctx.Err() {
t.Fatalf("expected error from context but got: %v", err)
}
}
func TestCancelAfterRequest(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
resp, err := doRequest(ctx)
// Cancel before reading the body.
// Request.Body should still be readable after the context is canceled.
cancel()
b, err := ioutil.ReadAll(resp.Body)
if err != nil || string(b) != requestBody {
t.Fatalf("could not read body: %q %v", b, err)
}
}
func doRequest(ctx context.Context) (*http.Response, error) {
var okHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(requestDuration)
w.Write([]byte(requestBody))
})
serv := httptest.NewServer(okHandler)
defer serv.Close()
return Get(ctx, nil, serv.URL)
}

View File

@ -284,8 +284,8 @@ func (t *table) push(i uint32, depth int) bool {
}
// The lists of element names and attribute keys were taken from
// http://www.whatwg.org/specs/web-apps/current-work/multipage/section-index.html
// as of the "HTML Living Standard - Last Updated 30 May 2012" version.
// https://html.spec.whatwg.org/multipage/indices.html#index
// as of the "HTML Living Standard - Last Updated 21 February 2015" version.
var elements = []string{
"a",
@ -352,6 +352,7 @@ var elements = []string{
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
@ -385,6 +386,7 @@ var elements = []string{
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
@ -400,7 +402,10 @@ var elements = []string{
"wbr",
}
// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
var attributes = []string{
"abbr",
"accept",
"accept-charset",
"accesskey",
@ -410,7 +415,6 @@ var attributes = []string{
"autocomplete",
"autofocus",
"autoplay",
"border",
"challenge",
"charset",
"checked",
@ -452,7 +456,7 @@ var attributes = []string{
"http-equiv",
"icon",
"id",
"inert",
"inputmode",
"ismap",
"itemid",
"itemprop",
@ -473,6 +477,7 @@ var attributes = []string{
"mediagroup",
"method",
"min",
"minlength",
"multiple",
"muted",
"name",
@ -500,6 +505,8 @@ var attributes = []string{
"shape",
"size",
"sizes",
"sortable",
"sorted",
"span",
"src",
"srcdoc",
@ -521,6 +528,8 @@ var attributes = []string{
var eventHandlers = []string{
"onabort",
"onautocomplete",
"onautocompleteerror",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
@ -552,6 +561,7 @@ var eventHandlers = []string{
"onkeydown",
"onkeypress",
"onkeyup",
"onlanguagechange",
"onload",
"onloadeddata",
"onloadedmetadata",
@ -580,11 +590,13 @@ var eventHandlers = []string{
"onseeking",
"onselect",
"onshow",
"onsort",
"onstalled",
"onstorage",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ package atom
var testAtomList = []string{
"a",
"abbr",
"abbr",
"accept",
"accept-charset",
"accesskey",
@ -33,7 +34,6 @@ var testAtomList = []string{
"blink",
"blockquote",
"body",
"border",
"br",
"button",
"canvas",
@ -125,8 +125,8 @@ var testAtomList = []string{
"iframe",
"image",
"img",
"inert",
"input",
"inputmode",
"ins",
"isindex",
"ismap",
@ -160,12 +160,14 @@ var testAtomList = []string{
"media",
"mediagroup",
"menu",
"menuitem",
"meta",
"meter",
"method",
"mglyph",
"mi",
"min",
"minlength",
"mn",
"mo",
"ms",
@ -183,6 +185,8 @@ var testAtomList = []string{
"ol",
"onabort",
"onafterprint",
"onautocomplete",
"onautocompleteerror",
"onbeforeprint",
"onbeforeunload",
"onblur",
@ -213,6 +217,7 @@ var testAtomList = []string{
"onkeydown",
"onkeypress",
"onkeyup",
"onlanguagechange",
"onload",
"onloadeddata",
"onloadedmetadata",
@ -241,11 +246,13 @@ var testAtomList = []string{
"onseeking",
"onselect",
"onshow",
"onsort",
"onstalled",
"onstorage",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
@ -291,6 +298,8 @@ var testAtomList = []string{
"size",
"sizes",
"small",
"sortable",
"sorted",
"source",
"spacer",
"span",
@ -315,6 +324,7 @@ var testAtomList = []string{
"target",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",

View File

@ -5,11 +5,12 @@
// Package charset provides common text encodings for HTML documents.
//
// The mapping from encoding labels to encodings is defined at
// http://encoding.spec.whatwg.org.
// https://encoding.spec.whatwg.org/.
package charset
import (
"bytes"
"fmt"
"io"
"mime"
"strings"
@ -110,6 +111,18 @@ func NewReader(r io.Reader, contentType string) (io.Reader, error) {
return r, nil
}
// NewReaderLabel returns a reader that converts from the specified charset to
// UTF-8. It uses Lookup to find the encoding that corresponds to label, and
// returns an error if Lookup returns nil. It is suitable for use as
// encoding/xml.Decoder's CharsetReader function.
func NewReaderLabel(label string, input io.Reader) (io.Reader, error) {
e, _ := Lookup(label)
if e == nil {
return nil, fmt.Errorf("unsupported charset: %q", label)
}
return transform.NewReader(input, e.NewDecoder()), nil
}
func prescan(content []byte) (e encoding.Encoding, name string) {
z := html.NewTokenizer(bytes.NewReader(content))
for {

View File

@ -6,6 +6,7 @@ package charset
import (
"bytes"
"encoding/xml"
"io/ioutil"
"runtime"
"strings"
@ -213,3 +214,23 @@ func TestFromMeta(t *testing.T) {
}
}
}
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

@ -6,7 +6,7 @@
package main
// Download http://encoding.spec.whatwg.org/encodings.json and use it to
// Download https://encoding.spec.whatwg.org/encodings.json and use it to
// generate table.go.
import (
@ -27,7 +27,7 @@ type group struct {
Heading string
}
const specURL = "http://encoding.spec.whatwg.org/encodings.json"
const specURL = "https://encoding.spec.whatwg.org/encodings.json"
func main() {
resp, err := http.Get(specURL)

View File

@ -1 +1,9 @@
These test cases come from http://www.w3.org/International/tests/html5/the-input-byte-stream/results-basics
These test cases come from
http://www.w3.org/International/tests/repository/html5/the-input-byte-stream/results-basics
Distributed under both the W3C Test Suite License
(http://www.w3.org/Consortium/Legal/2008/04-testsuite-license)
and the W3C 3-clause BSD License
(http://www.w3.org/Consortium/Legal/2008/03-bsd-license).
To contribute to a W3C Test Suite, see the policies and contribution
forms (http://www.w3.org/2004/10/27-testcases).

View File

@ -6,7 +6,7 @@ package html
// Section 12.2.3.2 of the HTML5 specification says "The following elements
// have varying levels of special parsing rules".
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements
// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
var isSpecialElementMap = map[string]bool{
"address": true,
"applet": true,
@ -24,7 +24,6 @@ var isSpecialElementMap = map[string]bool{
"center": true,
"col": true,
"colgroup": true,
"command": true,
"dd": true,
"details": true,
"dir": true,
@ -73,17 +72,20 @@ var isSpecialElementMap = map[string]bool{
"script": true,
"section": true,
"select": true,
"source": true,
"style": true,
"summary": true,
"table": true,
"tbody": true,
"td": true,
"template": true,
"textarea": true,
"tfoot": true,
"th": true,
"thead": true,
"title": true,
"tr": true,
"track": true,
"ul": true,
"wbr": true,
"xmp": true,

View File

@ -90,8 +90,8 @@ example, to process each anchor node in depth-first order:
f(doc)
The relevant specifications include:
http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html and
http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html
https://html.spec.whatwg.org/multipage/syntax.html and
https://html.spec.whatwg.org/multipage/syntax.html#tokenization
*/
package html

View File

@ -8,7 +8,7 @@ package html
const longestEntityWithoutSemicolon = 6
// entity is a map from HTML entity names to their values. The semicolon matters:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/named-character-references.html
// https://html.spec.whatwg.org/multipage/syntax.html#named-character-references
// lists both "amp" and "amp;" as two separate entries.
//
// Note that the HTML5 list is larger than the HTML4 list at

View File

@ -12,7 +12,7 @@ import (
// These replacements permit compatibility with old numeric entities that
// assumed Windows-1252 encoding.
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
var replacementTable = [...]rune{
'\u20AC', // First entry is what 0x80 should be replaced with.
'\u0081',
@ -55,7 +55,7 @@ var replacementTable = [...]rune{
// Precondition: b[src] == '&' && dst <= src.
// attribute should be true if parsing an attribute value.
func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
// i starts at 1 because we already know that s[0] == '&'.
i, s := 1, b[src:]

View File

@ -14,7 +14,7 @@ import (
)
// A parser implements the HTML5 parsing algorithm:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#tree-construction
// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction
type parser struct {
// tokenizer provides the tokens for the parser.
tokenizer *Tokenizer
@ -59,7 +59,7 @@ func (p *parser) top() *Node {
// Stop tags for use in popUntil. These come from section 12.2.3.2.
var (
defaultScopeStopTags = map[string][]a.Atom{
"": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object},
"": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template},
"math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext},
"svg": {a.Desc, a.ForeignObject, a.Title},
}
@ -1037,15 +1037,15 @@ func inBodyIM(p *parser) bool {
func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
// This is the "adoption agency" algorithm, described at
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#adoptionAgency
// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
// TODO: this is a fairly literal line-by-line translation of that algorithm.
// Once the code successfully parses the comprehensive test suite, we should
// refactor this code to be more idiomatic.
// Steps 1-3. The outer loop.
// Steps 1-4. The outer loop.
for i := 0; i < 8; i++ {
// Step 4. Find the formatting element.
// Step 5. Find the formatting element.
var formattingElement *Node
for j := len(p.afe) - 1; j >= 0; j-- {
if p.afe[j].Type == scopeMarkerNode {
@ -1070,7 +1070,7 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
return
}
// Steps 5-6. Find the furthest block.
// Steps 9-10. Find the furthest block.
var furthestBlock *Node
for _, e := range p.oe[feIndex:] {
if isSpecialElement(e) {
@ -1087,47 +1087,47 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
return
}
// Steps 7-8. Find the common ancestor and bookmark node.
// Steps 11-12. Find the common ancestor and bookmark node.
commonAncestor := p.oe[feIndex-1]
bookmark := p.afe.index(formattingElement)
// Step 9. The inner loop. Find the lastNode to reparent.
// Step 13. The inner loop. Find the lastNode to reparent.
lastNode := furthestBlock
node := furthestBlock
x := p.oe.index(node)
// Steps 9.1-9.3.
// Steps 13.1-13.2
for j := 0; j < 3; j++ {
// Step 9.4.
// Step 13.3.
x--
node = p.oe[x]
// Step 9.5.
// Step 13.4 - 13.5.
if p.afe.index(node) == -1 {
p.oe.remove(node)
continue
}
// Step 9.6.
// Step 13.6.
if node == formattingElement {
break
}
// Step 9.7.
// Step 13.7.
clone := node.clone()
p.afe[p.afe.index(node)] = clone
p.oe[p.oe.index(node)] = clone
node = clone
// Step 9.8.
// Step 13.8.
if lastNode == furthestBlock {
bookmark = p.afe.index(node) + 1
}
// Step 9.9.
// Step 13.9.
if lastNode.Parent != nil {
lastNode.Parent.RemoveChild(lastNode)
}
node.AppendChild(lastNode)
// Step 9.10.
// Step 13.10.
lastNode = node
}
// Step 10. Reparent lastNode to the common ancestor,
// Step 14. Reparent lastNode to the common ancestor,
// or for misnested table nodes, to the foster parent.
if lastNode.Parent != nil {
lastNode.Parent.RemoveChild(lastNode)
@ -1139,13 +1139,13 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
commonAncestor.AppendChild(lastNode)
}
// Steps 11-13. Reparent nodes from the furthest block's children
// Steps 15-17. Reparent nodes from the furthest block's children
// to a clone of the formatting element.
clone := formattingElement.clone()
reparentChildren(clone, furthestBlock)
furthestBlock.AppendChild(clone)
// Step 14. Fix up the list of active formatting elements.
// Step 18. Fix up the list of active formatting elements.
if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
// Move the bookmark with the rest of the list.
bookmark--
@ -1153,13 +1153,15 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
p.afe.remove(formattingElement)
p.afe.insert(bookmark, clone)
// Step 15. Fix up the stack of open elements.
// Step 19. Fix up the stack of open elements.
p.oe.remove(formattingElement)
p.oe.insert(p.oe.index(furthestBlock)+1, clone)
}
}
// 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
// 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-- {
if p.oe[i].DataAtom == tagAtom {

View File

@ -14,7 +14,7 @@ import (
type writer interface {
io.Writer
WriteByte(c byte) error // in Go 1.1, use io.ByteWriter
io.ByteWriter
WriteString(string) (int, error)
}

View File

@ -392,7 +392,7 @@ var tokenTests = []tokenTest{
"½",
},
// Attribute tests:
// http://dev.w3.org/html5/spec/Overview.html#attributes-0
// http://dev.w3.org/html5/pf-summary/Overview.html#attributes
{
"Empty attribute",
`<input disabled FOO>`,

View File

@ -64,6 +64,20 @@ func Dial(url_, protocol, origin string) (ws *Conn, err error) {
return DialConfig(config)
}
var portMap = map[string]string{
"ws": "80",
"wss": "443",
}
func parseAuthority(location *url.URL) string {
if _, ok := portMap[location.Scheme]; ok {
if _, _, err := net.SplitHostPort(location.Host); err != nil {
return net.JoinHostPort(location.Host, portMap[location.Scheme])
}
}
return location.Host
}
// DialConfig opens a new client connection to a WebSocket with a config.
func DialConfig(config *Config) (ws *Conn, err error) {
var client net.Conn
@ -75,10 +89,10 @@ func DialConfig(config *Config) (ws *Conn, err error) {
}
switch config.Location.Scheme {
case "ws":
client, err = net.Dial("tcp", config.Location.Host)
client, err = net.Dial("tcp", parseAuthority(config.Location))
case "wss":
client, err = tls.Dial("tcp", config.Location.Host, config.TlsConfig)
client, err = tls.Dial("tcp", parseAuthority(config.Location), config.TlsConfig)
default:
err = ErrBadScheme
@ -89,6 +103,7 @@ func DialConfig(config *Config) (ws *Conn, err error) {
ws, err = NewClient(config, client)
if err != nil {
client.Close()
goto Error
}
return

View File

@ -157,6 +157,9 @@ func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error
if err != nil {
return
}
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
b &= 0x7f
}
header = append(header, b)
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
}
@ -264,7 +267,7 @@ type hybiFrameHandler struct {
payloadType byte
}
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
if handler.conn.IsServerConn() {
// The client MUST mask all frames sent to the server.
if frame.(*hybiFrameReader).header.MaskingKey == nil {
@ -288,20 +291,19 @@ func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader,
handler.payloadType = frame.PayloadType()
case CloseFrame:
return nil, io.EOF
case PingFrame:
pingMsg := make([]byte, maxControlFramePayloadLength)
n, err := io.ReadFull(frame, pingMsg)
if err != nil && err != io.ErrUnexpectedEOF {
case PingFrame, PongFrame:
b := make([]byte, maxControlFramePayloadLength)
n, err := io.ReadFull(frame, b)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return nil, err
}
io.Copy(ioutil.Discard, frame)
n, err = handler.WritePong(pingMsg[:n])
if err != nil {
return nil, err
if frame.PayloadType() == PingFrame {
if _, err := handler.WritePong(b[:n]); err != nil {
return nil, err
}
}
return nil, nil
case PongFrame:
return nil, ErrNotImplemented
}
return frame, nil
}
@ -370,6 +372,23 @@ func generateNonce() (nonce []byte) {
return
}
// removeZone removes IPv6 zone identifer from host.
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
func removeZone(host string) string {
if !strings.HasPrefix(host, "[") {
return host
}
i := strings.LastIndex(host, "]")
if i < 0 {
return host
}
j := strings.LastIndex(host[:i], "%")
if j < 0 {
return host
}
return host[:j] + host[i:]
}
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
func getNonceAccept(nonce []byte) (expected []byte, err error) {
@ -389,7 +408,10 @@ func getNonceAccept(nonce []byte) (expected []byte, err error) {
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
bw.WriteString("Host: " + config.Location.Host + "\r\n")
// According to RFC 6874, an HTTP client, proxy, or other
// intermediary must remove any IPv6 zone identifier attached
// to an outgoing URI.
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
bw.WriteString("Upgrade: websocket\r\n")
bw.WriteString("Connection: Upgrade\r\n")
nonce := generateNonce()
@ -515,15 +537,15 @@ func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Reques
return http.StatusSwitchingProtocols, nil
}
// Origin parses Origin header in "req".
// If origin is "null", returns (nil, nil).
// Origin parses the Origin header in req.
// If the Origin header is not set, it returns nil and nil.
func Origin(config *Config, req *http.Request) (*url.URL, error) {
var origin string
switch config.Version {
case ProtocolVersionHybi13:
origin = req.Header.Get("Origin")
}
if origin == "null" {
if origin == "" {
return nil, nil
}
return url.ParseRequestURI(origin)

View File

@ -31,63 +31,74 @@ func TestSecWebSocketAccept(t *testing.T) {
}
func TestHybiClientHandshake(t *testing.T) {
b := bytes.NewBuffer([]byte{})
bw := bufio.NewWriter(b)
br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
type test struct {
url, host string
}
tests := []test{
{"ws://server.example.com/chat", "server.example.com"},
{"ws://127.0.0.1/chat", "127.0.0.1"},
}
if _, err := url.ParseRequestURI("http://[fe80::1%25lo0]"); err == nil {
tests = append(tests, test{"ws://[fe80::1%25lo0]/chat", "[fe80::1]"})
}
for _, tt := range tests {
var b bytes.Buffer
bw := bufio.NewWriter(&b)
br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
`))
var err error
config := new(Config)
config.Location, err = url.ParseRequestURI("ws://server.example.com/chat")
if err != nil {
t.Fatal("location url", err)
}
config.Origin, err = url.ParseRequestURI("http://example.com")
if err != nil {
t.Fatal("origin url", err)
}
config.Protocol = append(config.Protocol, "chat")
config.Protocol = append(config.Protocol, "superchat")
config.Version = ProtocolVersionHybi13
config.handshakeData = map[string]string{
"key": "dGhlIHNhbXBsZSBub25jZQ==",
}
err = hybiClientHandshake(config, br, bw)
if err != nil {
t.Errorf("handshake failed: %v", err)
}
req, err := http.ReadRequest(bufio.NewReader(b))
if err != nil {
t.Fatalf("read request: %v", err)
}
if req.Method != "GET" {
t.Errorf("request method expected GET, but got %q", req.Method)
}
if req.URL.Path != "/chat" {
t.Errorf("request path expected /chat, but got %q", req.URL.Path)
}
if req.Proto != "HTTP/1.1" {
t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
}
if req.Host != "server.example.com" {
t.Errorf("request Host expected server.example.com, but got %v", req.Host)
}
var expectedHeader = map[string]string{
"Connection": "Upgrade",
"Upgrade": "websocket",
"Sec-Websocket-Key": config.handshakeData["key"],
"Origin": config.Origin.String(),
"Sec-Websocket-Protocol": "chat, superchat",
"Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13),
}
for k, v := range expectedHeader {
if req.Header.Get(k) != v {
t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
var err error
var config Config
config.Location, err = url.ParseRequestURI(tt.url)
if err != nil {
t.Fatal("location url", err)
}
config.Origin, err = url.ParseRequestURI("http://example.com")
if err != nil {
t.Fatal("origin url", err)
}
config.Protocol = append(config.Protocol, "chat")
config.Protocol = append(config.Protocol, "superchat")
config.Version = ProtocolVersionHybi13
config.handshakeData = map[string]string{
"key": "dGhlIHNhbXBsZSBub25jZQ==",
}
if err := hybiClientHandshake(&config, br, bw); err != nil {
t.Fatal("handshake", err)
}
req, err := http.ReadRequest(bufio.NewReader(&b))
if err != nil {
t.Fatal("read request", err)
}
if req.Method != "GET" {
t.Errorf("request method expected GET, but got %s", req.Method)
}
if req.URL.Path != "/chat" {
t.Errorf("request path expected /chat, but got %s", req.URL.Path)
}
if req.Proto != "HTTP/1.1" {
t.Errorf("request proto expected HTTP/1.1, but got %s", req.Proto)
}
if req.Host != tt.host {
t.Errorf("request host expected %s, but got %s", tt.host, req.Host)
}
var expectedHeader = map[string]string{
"Connection": "Upgrade",
"Upgrade": "websocket",
"Sec-Websocket-Key": config.handshakeData["key"],
"Origin": config.Origin.String(),
"Sec-Websocket-Protocol": "chat, superchat",
"Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13),
}
for k, v := range expectedHeader {
if req.Header.Get(k) != v {
t.Errorf("%s expected %s, but got %v", k, v, req.Header.Get(k))
}
}
}
}
@ -326,7 +337,7 @@ func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []by
}
payload := make([]byte, len(testPayload))
_, err = r.Read(payload)
if err != nil {
if err != nil && err != io.EOF {
t.Errorf("read %v", err)
}
if !bytes.Equal(testPayload, payload) {
@ -363,13 +374,20 @@ func TestHybiShortBinaryFrame(t *testing.T) {
}
func TestHybiControlFrame(t *testing.T) {
frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame}
payload := []byte("hello")
frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame}
testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader)
frameHeader = &hybiFrameHeader{Fin: true, OpCode: PingFrame}
testHybiFrame(t, []byte{0x89, 0x00}, nil, nil, frameHeader)
frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader)
frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
testHybiFrame(t, []byte{0x8A, 0x00}, nil, nil, frameHeader)
frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame}
payload = []byte{0x03, 0xe8} // 1000
testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader)

View File

@ -74,7 +74,6 @@ func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
rwc, buf, err := w.(http.Hijacker).Hijack()
if err != nil {
panic("Hijack failed: " + err.Error())
return
}
// The server should abort the WebSocket connection if it finds
// the client did not send a handshake that matches with protocol
@ -95,8 +94,8 @@ func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
// You might want to verify websocket.Conn.Config().Origin in the func.
// If you use Server instead of Handler, you could call websocket.Origin and
// check the origin in your Handshake func. So, if you want to accept
// non-browser client, which doesn't send Origin header, you could use Server
//. that doesn't check origin in its Handshake.
// non-browser clients, which do not send an Origin header, set a
// Server.Handshake that does not check the origin.
type Handler func(*Conn)
func checkOrigin(config *Config, req *http.Request) (err error) {

View File

@ -216,10 +216,11 @@ func (ws *Conn) Write(msg []byte) (n int, err error) {
// Close implements the io.Closer interface.
func (ws *Conn) Close() error {
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
err1 := ws.rwc.Close()
if err != nil {
return err
}
return ws.rwc.Close()
return err1
}
func (ws *Conn) IsClientConn() bool { return ws.request == nil }

View File

@ -13,15 +13,21 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"runtime"
"strings"
"sync"
"testing"
"time"
)
var serverAddr string
var once sync.Once
func echoServer(ws *Conn) { io.Copy(ws, ws) }
func echoServer(ws *Conn) {
defer ws.Close()
io.Copy(ws, ws)
}
type Count struct {
S string
@ -29,6 +35,7 @@ type Count struct {
}
func countServer(ws *Conn) {
defer ws.Close()
for {
var count Count
err := JSON.Receive(ws, &count)
@ -44,6 +51,55 @@ func countServer(ws *Conn) {
}
}
type testCtrlAndDataHandler struct {
hybiFrameHandler
}
func (h *testCtrlAndDataHandler) WritePing(b []byte) (int, error) {
h.hybiFrameHandler.conn.wio.Lock()
defer h.hybiFrameHandler.conn.wio.Unlock()
w, err := h.hybiFrameHandler.conn.frameWriterFactory.NewFrameWriter(PingFrame)
if err != nil {
return 0, err
}
n, err := w.Write(b)
w.Close()
return n, err
}
func ctrlAndDataServer(ws *Conn) {
defer ws.Close()
h := &testCtrlAndDataHandler{hybiFrameHandler: hybiFrameHandler{conn: ws}}
ws.frameHandler = h
go func() {
for i := 0; ; i++ {
var b []byte
if i%2 != 0 { // with or without payload
b = []byte(fmt.Sprintf("#%d-CONTROL-FRAME-FROM-SERVER", i))
}
if _, err := h.WritePing(b); err != nil {
break
}
if _, err := h.WritePong(b); err != nil { // unsolicited pong
break
}
time.Sleep(10 * time.Millisecond)
}
}()
b := make([]byte, 128)
for {
n, err := ws.Read(b)
if err != nil {
break
}
if _, err := ws.Write(b[:n]); err != nil {
break
}
}
}
func subProtocolHandshake(config *Config, req *http.Request) error {
for _, proto := range config.Protocol {
if proto == "chat" {
@ -63,6 +119,7 @@ func subProtoServer(ws *Conn) {
func startServer() {
http.Handle("/echo", Handler(echoServer))
http.Handle("/count", Handler(countServer))
http.Handle("/ctrldata", Handler(ctrlAndDataServer))
subproto := Server{
Handshake: subProtocolHandshake,
Handler: Handler(subProtoServer),
@ -339,3 +396,192 @@ func TestSmallBuffer(t *testing.T) {
}
conn.Close()
}
var parseAuthorityTests = []struct {
in *url.URL
out string
}{
{
&url.URL{
Scheme: "ws",
Host: "www.google.com",
},
"www.google.com:80",
},
{
&url.URL{
Scheme: "wss",
Host: "www.google.com",
},
"www.google.com:443",
},
{
&url.URL{
Scheme: "ws",
Host: "www.google.com:80",
},
"www.google.com:80",
},
{
&url.URL{
Scheme: "wss",
Host: "www.google.com:443",
},
"www.google.com:443",
},
// some invalid ones for parseAuthority. parseAuthority doesn't
// concern itself with the scheme unless it actually knows about it
{
&url.URL{
Scheme: "http",
Host: "www.google.com",
},
"www.google.com",
},
{
&url.URL{
Scheme: "http",
Host: "www.google.com:80",
},
"www.google.com:80",
},
{
&url.URL{
Scheme: "asdf",
Host: "127.0.0.1",
},
"127.0.0.1",
},
{
&url.URL{
Scheme: "asdf",
Host: "www.google.com",
},
"www.google.com",
},
}
func TestParseAuthority(t *testing.T) {
for _, tt := range parseAuthorityTests {
out := parseAuthority(tt.in)
if out != tt.out {
t.Errorf("got %v; want %v", out, tt.out)
}
}
}
type closerConn struct {
net.Conn
closed int // count of the number of times Close was called
}
func (c *closerConn) Close() error {
c.closed++
return c.Conn.Close()
}
func TestClose(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("see golang.org/issue/11454")
}
once.Do(startServer)
conn, err := net.Dial("tcp", serverAddr)
if err != nil {
t.Fatal("dialing", err)
}
cc := closerConn{Conn: conn}
client, err := NewClient(newConfig(t, "/echo"), &cc)
if err != nil {
t.Fatalf("WebSocket handshake: %v", err)
}
// set the deadline to ten minutes ago, which will have expired by the time
// client.Close sends the close status frame.
conn.SetDeadline(time.Now().Add(-10 * time.Minute))
if err := client.Close(); err == nil {
t.Errorf("ws.Close(): expected error, got %v", err)
}
if cc.closed < 1 {
t.Fatalf("ws.Close(): expected underlying ws.rwc.Close to be called > 0 times, got: %v", cc.closed)
}
}
var originTests = []struct {
req *http.Request
origin *url.URL
}{
{
req: &http.Request{
Header: http.Header{
"Origin": []string{"http://www.example.com"},
},
},
origin: &url.URL{
Scheme: "http",
Host: "www.example.com",
},
},
{
req: &http.Request{},
},
}
func TestOrigin(t *testing.T) {
conf := newConfig(t, "/echo")
conf.Version = ProtocolVersionHybi13
for i, tt := range originTests {
origin, err := Origin(conf, tt.req)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(origin, tt.origin) {
t.Errorf("#%d: got origin %v; want %v", i, origin, tt.origin)
continue
}
}
}
func TestCtrlAndData(t *testing.T) {
once.Do(startServer)
c, err := net.Dial("tcp", serverAddr)
if err != nil {
t.Fatal(err)
}
ws, err := NewClient(newConfig(t, "/ctrldata"), c)
if err != nil {
t.Fatal(err)
}
defer ws.Close()
h := &testCtrlAndDataHandler{hybiFrameHandler: hybiFrameHandler{conn: ws}}
ws.frameHandler = h
b := make([]byte, 128)
for i := 0; i < 2; i++ {
data := []byte(fmt.Sprintf("#%d-DATA-FRAME-FROM-CLIENT", i))
if _, err := ws.Write(data); err != nil {
t.Fatalf("#%d: %v", i, err)
}
var ctrl []byte
if i%2 != 0 { // with or without payload
ctrl = []byte(fmt.Sprintf("#%d-CONTROL-FRAME-FROM-CLIENT", i))
}
if _, err := h.WritePing(ctrl); err != nil {
t.Fatalf("#%d: %v", i, err)
}
n, err := ws.Read(b)
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
if !bytes.Equal(b[:n], data) {
t.Fatalf("#%d: got %v; want %v", i, b[:n], data)
}
}
}