diff --git a/vendor/github.com/richardlehane/xmldetect/.travis.yml b/vendor/github.com/richardlehane/xmldetect/.travis.yml new file mode 100644 index 000000000..7a6c6aa4d --- /dev/null +++ b/vendor/github.com/richardlehane/xmldetect/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - tip \ No newline at end of file diff --git a/vendor/github.com/richardlehane/xmldetect/README.md b/vendor/github.com/richardlehane/xmldetect/README.md new file mode 100644 index 000000000..68ebf248c --- /dev/null +++ b/vendor/github.com/richardlehane/xmldetect/README.md @@ -0,0 +1,25 @@ +A library to detect whether the first and last bytes of a stream are valid xml and to report opening and closing tags, default namespaces, and the presence of an xml declaration. + +Example usage: + +```go +var ex = ` + + + "> + ]> + + &e; +` +dec, tag, ns, err := xmldetect.Root(strings.NewReader(ex)) +if err == nil && dec && tag == "doc" && ns == "ex" { + fmt.Println("root tag is 'doc' with namespace 'ex'") +} +``` + +Install with `go get github.com/richardlehane/xmldetect` + +[![Build Status](https://travis-ci.org/richardlehane/xmldetect.png?branch=master)](https://travis-ci.org/richardlehane/xmldetect) [![GoDoc](https://godoc.org/github.com/richardlehane/xmldetect?status.svg)](https://godoc.org/github.com/richardlehane/xmldetect) + +Licensed under the Apache License, Version 2.0 diff --git a/vendor/github.com/richardlehane/xmldetect/xmldetect.go b/vendor/github.com/richardlehane/xmldetect/xmldetect.go new file mode 100644 index 000000000..5456207df --- /dev/null +++ b/vendor/github.com/richardlehane/xmldetect/xmldetect.go @@ -0,0 +1,408 @@ +// Copyright 2015 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package xmldetect + +import ( + "errors" + "io" +) + +var ErrInvalid = errors.New("invalid XML") + +// Root inspects the beginning of an XML document and returns: +// - whether it has a +// +// "> +// ]> +// +// &e;` +// dec, tag, ns, err := Root(strings.NewReader(ex)) +// if err == nil && dec && tag == "doc" && ns == "ex" { +// fmt.Println("root tag is 'doc' with namespace 'ex'") +// } +func Root(in io.ByteReader) (declaration bool, tag, ns string, err error) { + for c, e := in.ReadByte(); e == nil; c, e = in.ReadByte() { + switch c { + default: + return false, "", "", ErrInvalid + // beginning of a token + case '<': + d, t, n, e := eatToken(in) + if e != nil { + return false, "", "", e + } + if d { + declaration = true + } + if t != "" { + tag, ns = t, n + return + } + // ignore whitespace + case ' ', '\r', '\n', '\t': + } + } + return false, "", "", ErrInvalid +} + +// Closing inspects the end of an XML document and returns the closing tag. +// The supplied byte reader should read bytes from the XML document in reverse, +// e.g. see the reverseReader in the example below. +// +// Example: +// type reverseReader struct { +// str string +// idx int +// } +// func (rr *reverseReader) ReadByte() (c byte, err error) { +// if rr.idx > len(rr.str)-1 { +// return 0, io.EOF +// } +// rr.idx++ +// return rr.str[len(rr.str)-rr.idx], nil +// } +// var ex = ` +// +// +// "> +// ]> +// &e; +// +// +// ` +// tag, err := Closing(&reverseReader{str: ex}) +// if err == nil && tag == "doc" { +// fmt.Println("closing tag is 'doc'") +// } +func Closing(in io.ByteReader) (tag string, err error) { + for c, e := in.ReadByte(); e == nil; c, e = in.ReadByte() { + switch c { + default: + return "", ErrInvalid + case '>': + t, e := eatBackToken(in) + if e != nil { + return "", e + } + if t != "" { + return t, nil + } + // ignore whitespace + case ' ', '\r', '\n', '\t': + } + } + return "", ErrInvalid +} + +var ( + piClose = []byte("?>") + commentClose = []byte("-->") + commentStart = []byte("--!<") + piStart = []byte("?<") +) + +// must eat will consume the ByteReader until the expected bytes are reached. +// Returns true if expected bytes are reached. +func mustEat(in io.ByteReader, expect []byte) bool { + var idx int + for { + c, err := in.ReadByte() + if err != nil { + return false + } + // happy case + if c == expect[idx] { + if idx == len(expect)-1 { + return true + } + idx++ + continue + } + // no match yet + if idx == 0 { + continue + } + // backtrack + for i := idx; i > 0; i-- { + if c == expect[i-1] { + subseq := true + for n, b := range expect[:i-1] { + if b != expect[idx-i+n] { + subseq = false + break + } + } + if subseq { + idx = i + break + } + } + } + } +} + +// eats a single token returning: +// a bool, if it is an xml processing instruction +// a tag and optional default namespace, if an element +// and an error +func eatToken(in io.ByteReader) (bool, string, string, error) { + c, err := in.ReadByte() + if err != nil { + return false, "", "", ErrInvalid + } + switch c { + default: + valid, tag, ns := eatElement(in, c) + if valid { + return false, tag, ns, nil + } + case '!': + // can be a comment or a DOCTYPE + if eatComment(in) { + return false, "", "", nil + } + case '?': + valid, decl := eatPI(in) + if valid { + return decl, "", "", nil + } + case '<', '>', ' ', '\r', '\n', '\t': + } + return false, "", "", ErrInvalid +} + +// eats an element returning: +// a bool to signfify if a valid element +// the tag name +// the default (xmlns) namespace. +func eatElement(in io.ByteReader, c byte) (bool, string, string) { + buf := make([]byte, 32) + var ( + err error + tag, ns string + idx int + ) + for ; err == nil; c, err = in.ReadByte() { + switch c { + default: + if idx >= len(buf) { + cp := make([]byte, len(buf)*2) + copy(cp, buf) + buf = cp + } + buf[idx] = c + idx++ + case '>', ' ', '\r', '\n', '\t': + if tag == "" { + tag = string(buf[:idx]) + } else if ns == "" { + ns = extractNS(buf[:idx]) + } + if c == '>' { + return true, tag, ns + } + idx = 0 + } + } + return false, "", "" +} + +func extractNS(buf []byte) string { + if len(buf) > 8 && + (buf[0] == 'x' || buf[0] == 'X') && + (buf[1] == 'm' || buf[1] == 'M') && + (buf[2] == 'l' || buf[2] == 'L') && + (buf[3] == 'n' || buf[3] == 'N') && + (buf[4] == 's' || buf[4] == 'S') { + var ( + inQuote byte + start int + ) + for i, c := range buf[5:] { + if inQuote == 0 { + // test for " or ' + if c == 0x27 || c == 0x22 { + inQuote = c + start = 5 + i + 1 + } + } else if c == inQuote { + return string(buf[start : i+5]) + } + } + } + return "" +} + +// eats a processing instruction, returning: +// - validity +// - whether it is an xml declaration i.e. +func eatPI(in io.ByteReader) (bool, bool) { + a, err := in.ReadByte() + if err != nil { + return false, false + } + b, err := in.ReadByte() + if err != nil { + return false, false + } + if a == '?' && b == '>' { + return true, false + } + c, err := in.ReadByte() + if err != nil { + return false, false + } + if b == '?' && c == '>' { + return true, false + } + for c == '?' { + c, err = in.ReadByte() + if err != nil { + return false, false + } + if c == '>' { + return true, false + } + } + var declaration bool + if (a == 'x' || a == 'X') && + (b == 'm' || b == 'M') && + (c == 'l' || c == 'L') { + declaration = true + } + return mustEat(in, piClose), declaration +} + +func eatComment(in io.ByteReader) bool { + c, err := in.ReadByte() + if err != nil { + return false + } + // comment or DOCTYPE? + switch c { + case '-': + c, _ = in.ReadByte() + if c != '-' { + return false + } + return mustEat(in, commentClose) + case 'D': + return eatDOCTYPE(in) + default: + return false + } +} + +func eatDOCTYPE(in io.ByteReader) bool { + expect := make([]byte, 6) + for i := range expect { + c, err := in.ReadByte() + if err != nil { + return false + } + expect[i] = c + } + if string(expect) != "OCTYPE" { + return false + } + var depth int + for { + c, err := in.ReadByte() + if err != nil { + return false + } + switch c { + case '>': + if depth == 0 { + return true + } + depth-- + case '<': + depth++ + } + } +} + +// eats a token in reverse, returning the closing tag name and an error. +func eatBackToken(in io.ByteReader) (string, error) { + c, err := in.ReadByte() + if err != nil { + return "", ErrInvalid + } + switch c { + default: + valid, tag := eatBackElement(in, c) + if valid { + return tag, nil + } + case '-': + c, err = in.ReadByte() + if err == nil && c == '-' && mustEat(in, commentStart) { + return "", nil + } + case '?': + if mustEat(in, piStart) { + return "", nil + } + case '<', '>': + } + return "", ErrInvalid +} + +func eatBackElement(in io.ByteReader, c byte) (bool, string) { + buf := make([]byte, 32) + var ( + err error + tag string + idx int + ) + var hasEnd bool + for ; err == nil; c, err = in.ReadByte() { + switch c { + default: + if idx >= len(buf) { + cp := make([]byte, len(buf)*2) + copy(cp, buf) + buf = cp + } + buf[idx] = c + idx++ + case '/': + hasEnd = true + case '<', ' ', '\r', '\n', '\t': + if idx > 0 { + // reverse the tag name + for left, right := 0, len(buf[:idx])-1; left < right; left, right = left+1, right-1 { + buf[left], buf[right] = buf[right], buf[left] + } + tag = string(buf[:idx]) + } + if c == '<' { + return hasEnd, tag + } + idx = 0 + } + } + return false, "" +} diff --git a/vendor/github.com/richardlehane/xmldetect/xmldetect_test.go b/vendor/github.com/richardlehane/xmldetect/xmldetect_test.go new file mode 100644 index 000000000..5ce803302 --- /dev/null +++ b/vendor/github.com/richardlehane/xmldetect/xmldetect_test.go @@ -0,0 +1,142 @@ +package xmldetect + +import ( + "io" + "strings" + "testing" +) + +const ( + nofail = 1 << iota + errfail + decfail + rootfail + nsfail + closefail +) + +var tests = []struct { + fails int // which values in this test case are *deliberate* fails? + err error + declaration bool + root string + ns string + content string // sample xml +}{ + { + nofail, + nil, + true, + "hello", + "http://www.adele.com", + ` + + + + the other + + + `, + }, + { + nofail, + nil, + false, + "doc", + "", + ` + "> + ]> + &e; + + + `}, + { + decfail | rootfail | nsfail | closefail, + nil, + true, + "goodbye", + "http://www.pink.com", + ` + + + the other + + + `, + }, +} + +func TestRoot(t *testing.T) { + for idx, v := range tests { + d, r, ns, err := Root(strings.NewReader(v.content)) + // error + if err != v.err { + if v.fails&errfail != errfail { + t.Errorf("testcase %d: bad err, got %v, expected %v", idx, err, v.err) + } + } else if v.fails&errfail == errfail { + t.Errorf("testcase %d: bad err, got %v, expected %v to fail", idx, err, v.err) + } + // declaration + if d != v.declaration { + if v.fails&decfail != decfail { + t.Errorf("testcase %d: bad declaration, got %v, expected %v", idx, d, v.declaration) + } + } else if v.fails&decfail == decfail { + t.Errorf("testcase %d: bad declaration, got %v, expected %v to fail", idx, d, v.declaration) + } + // opening tag + if r != v.root { + if v.fails&rootfail != rootfail { + t.Errorf("testcase %d: bad root, got %s, expected %s", idx, r, v.root) + } + } else if v.fails&rootfail == rootfail { + t.Errorf("testcase %d: bad root, got %s, expected %s to fail", idx, r, v.root) + } + // namespace + if ns != v.ns { + if v.fails&nsfail != nsfail { + t.Errorf("testcase %d: bad namespace, got %s, expected %s", idx, ns, v.ns) + } + } else if v.fails&nsfail == nsfail { + t.Errorf("testcase %d: bad namespace, got %s, expected %s to fail", idx, ns, v.ns) + } + } +} + +type reverseReader struct { + str string + idx int +} + +func (rr *reverseReader) ReadByte() (c byte, err error) { + if rr.idx > len(rr.str)-1 { + return 0, io.EOF + } + rr.idx++ + return rr.str[len(rr.str)-rr.idx], nil +} + +func TestClosing(t *testing.T) { + for idx, v := range tests { + tag, err := Closing(&reverseReader{str: v.content}) + // error + if err != v.err { + if v.fails&errfail != errfail { + t.Errorf("testcase %d: bad err, got %v, expected %v", idx, err, v.err) + } + } else if v.fails&errfail == errfail { + t.Errorf("testcase %d: bad err, got %v, expected %v to fail", idx, err, v.err) + } + // closing tag + if tag != v.root { + if v.fails&closefail != closefail { + t.Errorf("testcase %d: bad closing tag, got %s, expected %s", idx, tag, v.root) + } + } else if v.fails&closefail == closefail { + t.Errorf("testcase %d: bad closing tag, got %s, expected %s to fail", idx, tag, v.root) + } + } +}