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"]
|
||||
path = _vendor/github.com/google/go-querystring
|
||||
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