mirror of https://github.com/kubernetes/kops.git
vendor github.com/tent/http-link-go
This commit is contained in:
parent
e15bcba149
commit
36b51a851e
|
@ -319,3 +319,6 @@
|
||||||
[submodule "_vendor/github.com/google/go-querystring"]
|
[submodule "_vendor/github.com/google/go-querystring"]
|
||||||
path = _vendor/github.com/google/go-querystring
|
path = _vendor/github.com/google/go-querystring
|
||||||
url = https://github.com/google/go-querystring.git
|
url = https://github.com/google/go-querystring.git
|
||||||
|
[submodule "_vendor/github.com/tent/http-link-go"]
|
||||||
|
path = _vendor/github.com/tent/http-link-go
|
||||||
|
url = https://github.com/tent/http-link-go.git
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ac974c61c2f990f4115b119354b5e0b47550e888
|
|
@ -0,0 +1 @@
|
||||||
|
*.test
|
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- tip
|
||||||
|
before_install:
|
||||||
|
- go get launchpad.net/gocheck
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2013 Tent.is, LLC. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Tent.is, LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,12 @@
|
||||||
|
# http-link-go [](https://travis-ci.org/tent/http-link-go)
|
||||||
|
|
||||||
|
http-link-go implements parsing and serialization of Link header values as
|
||||||
|
defined in [RFC 5988](https://tools.ietf.org/html/rfc5988).
|
||||||
|
|
||||||
|
[**Documentation**](http://godoc.org/github.com/tent/http-link-go)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```text
|
||||||
|
go get github.com/tent/http-link-go
|
||||||
|
```
|
|
@ -0,0 +1,185 @@
|
||||||
|
// Package link implements parsing and serialization of Link header values as
|
||||||
|
// defined in RFC 5988.
|
||||||
|
package link
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
URI string
|
||||||
|
Rel string
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format serializes a slice of Links into a header value. It does not currently
|
||||||
|
// implement RFC 2231 handling of non-ASCII character encoding and language
|
||||||
|
// information.
|
||||||
|
func Format(links []Link) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
for i, link := range links {
|
||||||
|
if i > 0 {
|
||||||
|
buf.Write([]byte(", "))
|
||||||
|
}
|
||||||
|
buf.WriteByte('<')
|
||||||
|
buf.WriteString(link.URI)
|
||||||
|
buf.WriteByte('>')
|
||||||
|
|
||||||
|
writeParam(buf, "rel", link.Rel)
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(link.Params))
|
||||||
|
for k := range link.Params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
writeParam(buf, k, link.Params[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeParam(buf *bytes.Buffer, key, value string) {
|
||||||
|
buf.Write([]byte("; "))
|
||||||
|
buf.WriteString(key)
|
||||||
|
buf.Write([]byte(`="`))
|
||||||
|
buf.WriteString(value)
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a Link header value into a slice of Links. It does not currently
|
||||||
|
// implement RFC 2231 handling of non-ASCII character encoding and language
|
||||||
|
// information.
|
||||||
|
func Parse(l string) ([]Link, error) {
|
||||||
|
v := []byte(l)
|
||||||
|
v = bytes.TrimSpace(v)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
links := make([]Link, 0, 1)
|
||||||
|
for len(v) > 0 {
|
||||||
|
if v[0] != '<' {
|
||||||
|
return nil, errors.New("link: does not start with <")
|
||||||
|
}
|
||||||
|
lend := bytes.IndexByte(v, '>')
|
||||||
|
if lend == -1 {
|
||||||
|
return nil, errors.New("link: does not contain ending >")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make(map[string]string)
|
||||||
|
link := Link{URI: string(v[1:lend]), Params: params}
|
||||||
|
links = append(links, link)
|
||||||
|
|
||||||
|
// trim off parsed url
|
||||||
|
v = v[lend+1:]
|
||||||
|
if len(v) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
v = bytes.TrimLeftFunc(v, unicode.IsSpace)
|
||||||
|
|
||||||
|
for len(v) > 0 {
|
||||||
|
if v[0] != ';' && v[0] != ',' {
|
||||||
|
return nil, errors.New(`link: expected ";" or "'", got "` + string(v[0:1]) + `"`)
|
||||||
|
}
|
||||||
|
var next bool
|
||||||
|
if v[0] == ',' {
|
||||||
|
next = true
|
||||||
|
}
|
||||||
|
v = bytes.TrimLeftFunc(v[1:], unicode.IsSpace)
|
||||||
|
if next || len(v) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var key, value []byte
|
||||||
|
key, value, v = consumeParam(v)
|
||||||
|
if key == nil || value == nil {
|
||||||
|
return nil, errors.New("link: malformed param")
|
||||||
|
}
|
||||||
|
if k := string(key); k == "rel" {
|
||||||
|
if links[len(links)-1].Rel == "" {
|
||||||
|
links[len(links)-1].Rel = string(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params[k] = string(value)
|
||||||
|
}
|
||||||
|
v = bytes.TrimLeftFunc(v, unicode.IsSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTokenChar(r rune) bool {
|
||||||
|
return r > 0x20 && r < 0x7f && r != '"' && r != ',' && r != '=' && r != ';'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNotTokenChar(r rune) bool { return !isTokenChar(r) }
|
||||||
|
|
||||||
|
func consumeToken(v []byte) (token, rest []byte) {
|
||||||
|
notPos := bytes.IndexFunc(v, isNotTokenChar)
|
||||||
|
if notPos == -1 {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
if notPos == 0 {
|
||||||
|
return nil, v
|
||||||
|
}
|
||||||
|
return v[0:notPos], v[notPos:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeValue(v []byte) (value, rest []byte) {
|
||||||
|
if v[0] != '"' {
|
||||||
|
return nil, v
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = v[1:]
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
var nextIsLiteral bool
|
||||||
|
for idx, r := range string(rest) {
|
||||||
|
switch {
|
||||||
|
case nextIsLiteral:
|
||||||
|
buffer.WriteRune(r)
|
||||||
|
nextIsLiteral = false
|
||||||
|
case r == '"':
|
||||||
|
return buffer.Bytes(), rest[idx+1:]
|
||||||
|
case r == '\\':
|
||||||
|
nextIsLiteral = true
|
||||||
|
case r != '\r' && r != '\n':
|
||||||
|
buffer.WriteRune(r)
|
||||||
|
default:
|
||||||
|
return nil, v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, v
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeParam(v []byte) (param, value, rest []byte) {
|
||||||
|
param, rest = consumeToken(v)
|
||||||
|
param = bytes.ToLower(param)
|
||||||
|
if param == nil {
|
||||||
|
return nil, nil, v
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = bytes.TrimLeftFunc(rest, unicode.IsSpace)
|
||||||
|
if len(rest) == 0 || rest[0] != '=' {
|
||||||
|
return nil, nil, v
|
||||||
|
}
|
||||||
|
rest = rest[1:] // consume equals sign
|
||||||
|
rest = bytes.TrimLeftFunc(rest, unicode.IsSpace)
|
||||||
|
if len(rest) == 0 {
|
||||||
|
return nil, nil, v
|
||||||
|
}
|
||||||
|
if rest[0] != '"' {
|
||||||
|
value, rest = consumeToken(rest)
|
||||||
|
} else {
|
||||||
|
value, rest = consumeValue(rest)
|
||||||
|
}
|
||||||
|
if value == nil {
|
||||||
|
return nil, nil, v
|
||||||
|
}
|
||||||
|
return param, value, rest
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package link
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "launchpad.net/gocheck"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hook up gocheck into the "go test" runner.
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type LinkSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&LinkSuite{})
|
||||||
|
|
||||||
|
// TODO: add more tests
|
||||||
|
var linkParseTests = []struct {
|
||||||
|
in string
|
||||||
|
out []Link
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"<http://example.com/TheBook/chapter2>; rel=\"previous\";\n title=\"previous chapter\"",
|
||||||
|
[]Link{{URI: "http://example.com/TheBook/chapter2", Rel: "previous", Params: map[string]string{"title": "previous chapter"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"</TheBook/chapter2>;\n rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel,\n </TheBook/chapter4>;\n rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel",
|
||||||
|
[]Link{
|
||||||
|
{URI: "/TheBook/chapter2", Rel: "previous", Params: map[string]string{"title*": "UTF-8'de'letztes%20Kapitel"}},
|
||||||
|
{URI: "/TheBook/chapter4", Rel: "next", Params: map[string]string{"title*": "UTF-8'de'n%c3%a4chstes%20Kapitel"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkSuite) TestLinkParsing(c *C) {
|
||||||
|
for i, t := range linkParseTests {
|
||||||
|
res, err := Parse(t.in)
|
||||||
|
c.Assert(err, IsNil, Commentf("test %d", i))
|
||||||
|
c.Assert(res, DeepEquals, t.out, Commentf("test %d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkFormatTests = []struct {
|
||||||
|
in []Link
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]Link{{URI: "/a", Rel: "foo", Params: map[string]string{"a": "b", "c": "d"}}},
|
||||||
|
`</a>; rel="foo"; a="b"; c="d"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]Link{
|
||||||
|
{URI: "/b", Rel: "foo", Params: map[string]string{"a": "b", "c": "d"}},
|
||||||
|
{URI: "/a", Rel: "foo", Params: map[string]string{"a": "b", "c": "d"}}},
|
||||||
|
`</b>; rel="foo"; a="b"; c="d", </a>; rel="foo"; a="b"; c="d"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkSuite) TestLinkGeneration(c *C) {
|
||||||
|
for i, t := range linkFormatTests {
|
||||||
|
res := Format(t.in)
|
||||||
|
cm := Commentf("test %d", i)
|
||||||
|
c.Assert(res, Equals, t.out, cm)
|
||||||
|
parsed, err := Parse(res)
|
||||||
|
c.Assert(err, IsNil, cm)
|
||||||
|
c.Assert(parsed, DeepEquals, t.in, cm)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue