mirror of
https://github.com/restic/rest-server.git
synced 2025-12-07 09:36:13 -08:00
Vendor dependencies
This commit is contained in:
committed by
Zlatko Čalušić
parent
2f0a16d8b7
commit
6054876201
51
vendor/goji.io/pat/match.go
generated
vendored
Normal file
51
vendor/goji.io/pat/match.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package pat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"goji.io/internal"
|
||||
"goji.io/pattern"
|
||||
)
|
||||
|
||||
type match struct {
|
||||
context.Context
|
||||
pat *Pattern
|
||||
matches []string
|
||||
}
|
||||
|
||||
func (m match) Value(key interface{}) interface{} {
|
||||
switch key {
|
||||
case pattern.AllVariables:
|
||||
var vs map[pattern.Variable]interface{}
|
||||
if vsi := m.Context.Value(key); vsi == nil {
|
||||
if len(m.pat.pats) == 0 {
|
||||
return nil
|
||||
}
|
||||
vs = make(map[pattern.Variable]interface{}, len(m.matches))
|
||||
} else {
|
||||
vs = vsi.(map[pattern.Variable]interface{})
|
||||
}
|
||||
|
||||
for _, p := range m.pat.pats {
|
||||
vs[p.name] = m.matches[p.idx]
|
||||
}
|
||||
return vs
|
||||
case internal.Path:
|
||||
if len(m.matches) == len(m.pat.pats)+1 {
|
||||
return m.matches[len(m.matches)-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if k, ok := key.(pattern.Variable); ok {
|
||||
i := sort.Search(len(m.pat.pats), func(i int) bool {
|
||||
return m.pat.pats[i].name >= k
|
||||
})
|
||||
if i < len(m.pat.pats) && m.pat.pats[i].name == k {
|
||||
return m.matches[m.pat.pats[i].idx]
|
||||
}
|
||||
}
|
||||
|
||||
return m.Context.Value(key)
|
||||
}
|
||||
60
vendor/goji.io/pat/match_test.go
generated
vendored
Normal file
60
vendor/goji.io/pat/match_test.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package pat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"goji.io/pattern"
|
||||
)
|
||||
|
||||
func TestExistingContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pat := New("/hi/:c/:a/:r/:l")
|
||||
req, err := http.NewRequest("GET", "/hi/foo/bar/baz/quux", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = pattern.SetPath(ctx, req.URL.EscapedPath())
|
||||
ctx = context.WithValue(ctx, pattern.AllVariables, map[pattern.Variable]interface{}{
|
||||
"hello": "world",
|
||||
"c": "nope",
|
||||
})
|
||||
ctx = context.WithValue(ctx, pattern.Variable("user"), "carl")
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
req = pat.Match(req)
|
||||
if req == nil {
|
||||
t.Fatalf("expected pattern to match")
|
||||
}
|
||||
ctx = req.Context()
|
||||
|
||||
expected := map[pattern.Variable]interface{}{
|
||||
"c": "foo",
|
||||
"a": "bar",
|
||||
"r": "baz",
|
||||
"l": "quux",
|
||||
}
|
||||
for k, v := range expected {
|
||||
if p := Param(req, string(k)); p != v {
|
||||
t.Errorf("expected %s=%q, got %q", k, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
expected["hello"] = "world"
|
||||
all := ctx.Value(pattern.AllVariables).(map[pattern.Variable]interface{})
|
||||
if !reflect.DeepEqual(all, expected) {
|
||||
t.Errorf("expected %v, got %v", expected, all)
|
||||
}
|
||||
|
||||
if path := pattern.Path(ctx); path != "" {
|
||||
t.Errorf("expected path=%q, got %q", "", path)
|
||||
}
|
||||
|
||||
if user := ctx.Value(pattern.Variable("user")); user != "carl" {
|
||||
t.Errorf("expected user=%q, got %q", "carl", user)
|
||||
}
|
||||
}
|
||||
51
vendor/goji.io/pat/methods.go
generated
vendored
Normal file
51
vendor/goji.io/pat/methods.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package pat
|
||||
|
||||
/*
|
||||
Delete returns a Pat route that only matches the DELETE HTTP method.
|
||||
*/
|
||||
func Delete(pat string) *Pattern {
|
||||
return newWithMethods(pat, "DELETE")
|
||||
}
|
||||
|
||||
/*
|
||||
Get returns a Pat route that only matches the GET and HEAD HTTP method. HEAD
|
||||
requests are handled transparently by net/http.
|
||||
*/
|
||||
func Get(pat string) *Pattern {
|
||||
return newWithMethods(pat, "GET", "HEAD")
|
||||
}
|
||||
|
||||
/*
|
||||
Head returns a Pat route that only matches the HEAD HTTP method.
|
||||
*/
|
||||
func Head(pat string) *Pattern {
|
||||
return newWithMethods(pat, "HEAD")
|
||||
}
|
||||
|
||||
/*
|
||||
Options returns a Pat route that only matches the OPTIONS HTTP method.
|
||||
*/
|
||||
func Options(pat string) *Pattern {
|
||||
return newWithMethods(pat, "OPTIONS")
|
||||
}
|
||||
|
||||
/*
|
||||
Patch returns a Pat route that only matches the PATCH HTTP method.
|
||||
*/
|
||||
func Patch(pat string) *Pattern {
|
||||
return newWithMethods(pat, "PATCH")
|
||||
}
|
||||
|
||||
/*
|
||||
Post returns a Pat route that only matches the POST HTTP method.
|
||||
*/
|
||||
func Post(pat string) *Pattern {
|
||||
return newWithMethods(pat, "POST")
|
||||
}
|
||||
|
||||
/*
|
||||
Put returns a Pat route that only matches the PUT HTTP method.
|
||||
*/
|
||||
func Put(pat string) *Pattern {
|
||||
return newWithMethods(pat, "PUT")
|
||||
}
|
||||
83
vendor/goji.io/pat/methods_test.go
generated
vendored
Normal file
83
vendor/goji.io/pat/methods_test.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package pat
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Delete("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was DELETE, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("DELETE", "/")) == nil {
|
||||
t.Errorf("pattern didn't match DELETE")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Get("/")
|
||||
if pat.Match(mustReq("POST", "/")) != nil {
|
||||
t.Errorf("pattern was GET, but matched POST")
|
||||
}
|
||||
if pat.Match(mustReq("GET", "/")) == nil {
|
||||
t.Errorf("pattern didn't match GET")
|
||||
}
|
||||
if pat.Match(mustReq("HEAD", "/")) == nil {
|
||||
t.Errorf("pattern didn't match HEAD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHead(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Head("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was HEAD, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("HEAD", "/")) == nil {
|
||||
t.Errorf("pattern didn't match HEAD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Options("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was OPTIONS, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("OPTIONS", "/")) == nil {
|
||||
t.Errorf("pattern didn't match OPTIONS")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Patch("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was PATCH, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("PATCH", "/")) == nil {
|
||||
t.Errorf("pattern didn't match PATCH")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPost(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Post("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was POST, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("POST", "/")) == nil {
|
||||
t.Errorf("pattern didn't match POST")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
t.Parallel()
|
||||
pat := Put("/")
|
||||
if pat.Match(mustReq("GET", "/")) != nil {
|
||||
t.Errorf("pattern was PUT, but matched GET")
|
||||
}
|
||||
if pat.Match(mustReq("PUT", "/")) == nil {
|
||||
t.Errorf("pattern didn't match PUT")
|
||||
}
|
||||
}
|
||||
302
vendor/goji.io/pat/pat.go
generated
vendored
Normal file
302
vendor/goji.io/pat/pat.go
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
Package pat is a URL-matching domain-specific language for Goji.
|
||||
|
||||
|
||||
Quick Reference
|
||||
|
||||
The following table gives an overview of the language this package accepts. See
|
||||
the subsequent sections for a more detailed explanation of what each pattern
|
||||
does.
|
||||
|
||||
Pattern Matches Does Not Match
|
||||
|
||||
/ / /hello
|
||||
|
||||
/hello /hello /hi
|
||||
/hello/
|
||||
|
||||
/user/:name /user/carl /user/carl/photos
|
||||
/user/alice /user/carl/
|
||||
/user/
|
||||
|
||||
/:file.:ext /data.json /.json
|
||||
/info.txt /data.
|
||||
/data.tar.gz /data.json/download
|
||||
|
||||
/user/* /user/ /user
|
||||
/user/carl
|
||||
/user/carl/photos
|
||||
|
||||
|
||||
Static Paths
|
||||
|
||||
Most URL paths may be specified directly: the pattern "/hello" matches URLs with
|
||||
precisely that path ("/hello/", for instance, is treated as distinct).
|
||||
|
||||
Note that this package operates on raw (i.e., escaped) paths (see the
|
||||
documentation for net/url.URL.EscapedPath). In order to match a character that
|
||||
can appear escaped in a URL path, use its percent-encoded form.
|
||||
|
||||
|
||||
Named Matches
|
||||
|
||||
Named matches allow URL paths to contain any value in a particular path segment.
|
||||
Such matches are denoted by a leading ":", for example ":name" in the rule
|
||||
"/user/:name", and permit any non-empty value in that position. For instance, in
|
||||
the previous "/user/:name" example, the path "/user/carl" is matched, while
|
||||
"/user/" or "/user/carl/" (note the trailing slash) are not matched. Pat rules
|
||||
can contain any number of named matches.
|
||||
|
||||
Named matches set URL variables by comparing pattern names to the segments they
|
||||
matched. In our "/user/:name" example, a request for "/user/carl" would bind the
|
||||
"name" variable to the value "carl". Use the Param function to extract these
|
||||
variables from the request context. Variable names in a single pattern must be
|
||||
unique.
|
||||
|
||||
Matches are ordinarily delimited by slashes ("/"), but several other characters
|
||||
are accepted as delimiters (with slightly different semantics): the period
|
||||
("."), semicolon (";"), and comma (",") characters. For instance, given the
|
||||
pattern "/:file.:ext", the request "/data.json" would match, binding "file" to
|
||||
"data" and "ext" to "json". Note that these special characters are treated
|
||||
slightly differently than slashes: the above pattern also matches the path
|
||||
"/data.tar.gz", with "ext" getting set to "tar.gz"; and the pattern "/:file"
|
||||
matches names with dots in them (like "data.json").
|
||||
|
||||
|
||||
Prefix Matches
|
||||
|
||||
Pat can also match prefixes of routes using wildcards. Prefix wildcard routes
|
||||
end with "/*", and match just the path segments preceding the asterisk. For
|
||||
instance, the pattern "/user/*" will match "/user/" and "/user/carl/photos" but
|
||||
not "/user" (note the lack of a trailing slash).
|
||||
|
||||
The unmatched suffix, including the leading slash ("/"), are placed into the
|
||||
request context, which allows subsequent routing (e.g., a subrouter) to continue
|
||||
from where this pattern left off. For instance, in the "/user/*" pattern from
|
||||
above, a request for "/user/carl/photos" will consume the "/user" prefix,
|
||||
leaving the path "/carl/photos" for subsequent patterns to handle. A subrouter
|
||||
pattern for "/:name/photos" would match this remaining path segment, for
|
||||
instance.
|
||||
*/
|
||||
package pat
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"goji.io/pattern"
|
||||
)
|
||||
|
||||
type patNames []struct {
|
||||
name pattern.Variable
|
||||
idx int
|
||||
}
|
||||
|
||||
func (p patNames) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
func (p patNames) Less(i, j int) bool {
|
||||
return p[i].name < p[j].name
|
||||
}
|
||||
func (p patNames) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
/*
|
||||
Pattern implements goji.Pattern using a path-matching domain specific language.
|
||||
See the package documentation for more information about the semantics of this
|
||||
object.
|
||||
*/
|
||||
type Pattern struct {
|
||||
raw string
|
||||
methods map[string]struct{}
|
||||
// These are parallel arrays of each pattern string (sans ":"), the
|
||||
// breaks each expect afterwords (used to support e.g., "." dividers),
|
||||
// and the string literals in between every pattern. There is always one
|
||||
// more literal than pattern, and they are interleaved like this:
|
||||
// <literal> <pattern> <literal> <pattern> <literal> etc...
|
||||
pats patNames
|
||||
breaks []byte
|
||||
literals []string
|
||||
wildcard bool
|
||||
}
|
||||
|
||||
// "Break characters" are characters that can end patterns. They are not allowed
|
||||
// to appear in pattern names. "/" was chosen because it is the standard path
|
||||
// separator, and "." was chosen because it often delimits file extensions. ";"
|
||||
// and "," were chosen because Section 3.3 of RFC 3986 suggests their use.
|
||||
const bc = "/.;,"
|
||||
|
||||
var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`)
|
||||
|
||||
/*
|
||||
New returns a new Pattern from the given Pat route. See the package
|
||||
documentation for more information about what syntax is accepted by this
|
||||
function.
|
||||
*/
|
||||
func New(pat string) *Pattern {
|
||||
p := &Pattern{raw: pat}
|
||||
|
||||
if strings.HasSuffix(pat, "/*") {
|
||||
pat = pat[:len(pat)-1]
|
||||
p.wildcard = true
|
||||
}
|
||||
|
||||
matches := patternRe.FindAllStringSubmatchIndex(pat, -1)
|
||||
numMatches := len(matches)
|
||||
p.pats = make(patNames, numMatches)
|
||||
p.breaks = make([]byte, numMatches)
|
||||
p.literals = make([]string, numMatches+1)
|
||||
|
||||
n := 0
|
||||
for i, match := range matches {
|
||||
a, b := match[2], match[3]
|
||||
p.literals[i] = pat[n : a-1] // Need to leave off the colon
|
||||
p.pats[i].name = pattern.Variable(pat[a:b])
|
||||
p.pats[i].idx = i
|
||||
if b == len(pat) {
|
||||
p.breaks[i] = '/'
|
||||
} else {
|
||||
p.breaks[i] = pat[b]
|
||||
}
|
||||
n = b
|
||||
}
|
||||
p.literals[numMatches] = pat[n:]
|
||||
|
||||
sort.Sort(p.pats)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newWithMethods(pat string, methods ...string) *Pattern {
|
||||
p := New(pat)
|
||||
|
||||
methodSet := make(map[string]struct{}, len(methods))
|
||||
for _, method := range methods {
|
||||
methodSet[method] = struct{}{}
|
||||
}
|
||||
p.methods = methodSet
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
/*
|
||||
Match runs the Pat pattern on the given request, returning a non-nil output
|
||||
request if the input request matches the pattern.
|
||||
|
||||
This function satisfies goji.Pattern.
|
||||
*/
|
||||
func (p *Pattern) Match(r *http.Request) *http.Request {
|
||||
if p.methods != nil {
|
||||
if _, ok := p.methods[r.Method]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check Path
|
||||
ctx := r.Context()
|
||||
path := pattern.Path(ctx)
|
||||
var scratch []string
|
||||
if p.wildcard {
|
||||
scratch = make([]string, len(p.pats)+1)
|
||||
} else if len(p.pats) > 0 {
|
||||
scratch = make([]string, len(p.pats))
|
||||
}
|
||||
|
||||
for i := range p.pats {
|
||||
sli := p.literals[i]
|
||||
if !strings.HasPrefix(path, sli) {
|
||||
return nil
|
||||
}
|
||||
path = path[len(sli):]
|
||||
|
||||
m := 0
|
||||
bc := p.breaks[i]
|
||||
for ; m < len(path); m++ {
|
||||
if path[m] == bc || path[m] == '/' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if m == 0 {
|
||||
// Empty strings are not matches, otherwise routes like
|
||||
// "/:foo" would match the path "/"
|
||||
return nil
|
||||
}
|
||||
scratch[i] = path[:m]
|
||||
path = path[m:]
|
||||
}
|
||||
|
||||
// There's exactly one more literal than pat.
|
||||
tail := p.literals[len(p.pats)]
|
||||
if p.wildcard {
|
||||
if !strings.HasPrefix(path, tail) {
|
||||
return nil
|
||||
}
|
||||
scratch[len(p.pats)] = path[len(tail)-1:]
|
||||
} else if path != tail {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range p.pats {
|
||||
var err error
|
||||
scratch[i], err = unescape(scratch[i])
|
||||
if err != nil {
|
||||
// If we encounter an encoding error here, there's
|
||||
// really not much we can do about it with our current
|
||||
// API, and I'm not really interested in supporting
|
||||
// clients that misencode URLs anyways.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return r.WithContext(&match{ctx, p, scratch})
|
||||
}
|
||||
|
||||
/*
|
||||
PathPrefix returns a string prefix that the Paths of all requests that this
|
||||
Pattern accepts must contain.
|
||||
|
||||
This function satisfies goji's PathPrefix Pattern optimization.
|
||||
*/
|
||||
func (p *Pattern) PathPrefix() string {
|
||||
return p.literals[0]
|
||||
}
|
||||
|
||||
/*
|
||||
HTTPMethods returns a set of HTTP methods that all requests that this
|
||||
Pattern matches must be in, or nil if it's not possible to determine
|
||||
which HTTP methods might be matched.
|
||||
|
||||
This function satisfies goji's HTTPMethods Pattern optimization.
|
||||
*/
|
||||
func (p *Pattern) HTTPMethods() map[string]struct{} {
|
||||
return p.methods
|
||||
}
|
||||
|
||||
/*
|
||||
String returns the pattern string that was used to create this Pattern.
|
||||
*/
|
||||
func (p *Pattern) String() string {
|
||||
return p.raw
|
||||
}
|
||||
|
||||
/*
|
||||
Param returns the bound parameter with the given name. For instance, given the
|
||||
route:
|
||||
|
||||
/user/:name
|
||||
|
||||
and the URL Path:
|
||||
|
||||
/user/carl
|
||||
|
||||
a call to Param(r, "name") would return the string "carl". It is the caller's
|
||||
responsibility to ensure that the variable has been bound. Attempts to access
|
||||
variables that have not been set (or which have been invalidly set) are
|
||||
considered programmer errors and will trigger a panic.
|
||||
*/
|
||||
func Param(r *http.Request, name string) string {
|
||||
return r.Context().Value(pattern.Variable(name)).(string)
|
||||
}
|
||||
170
vendor/goji.io/pat/pat_test.go
generated
vendored
Normal file
170
vendor/goji.io/pat/pat_test.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package pat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"goji.io/pattern"
|
||||
)
|
||||
|
||||
func mustReq(method, path string) *http.Request {
|
||||
req, err := http.NewRequest(method, path, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx := pattern.SetPath(context.Background(), req.URL.EscapedPath())
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
type PatTest struct {
|
||||
pat string
|
||||
req string
|
||||
match bool
|
||||
vars map[pattern.Variable]interface{}
|
||||
path string
|
||||
}
|
||||
|
||||
type pv map[pattern.Variable]interface{}
|
||||
|
||||
var PatTests = []PatTest{
|
||||
{"/", "/", true, nil, ""},
|
||||
{"/", "/hello", false, nil, ""},
|
||||
{"/hello", "/hello", true, nil, ""},
|
||||
|
||||
{"/:name", "/carl", true, pv{"name": "carl"}, ""},
|
||||
{"/:name", "/carl/", false, nil, ""},
|
||||
{"/:name", "/", false, nil, ""},
|
||||
{"/:name/", "/carl/", true, pv{"name": "carl"}, ""},
|
||||
{"/:name/", "/carl/no", false, nil, ""},
|
||||
{"/:name/hi", "/carl/hi", true, pv{"name": "carl"}, ""},
|
||||
{"/:name/:color", "/carl/red", true, pv{"name": "carl", "color": "red"}, ""},
|
||||
{"/:name/:color", "/carl/", false, nil, ""},
|
||||
{"/:name/:color", "/carl.red", false, nil, ""},
|
||||
|
||||
{"/:file.:ext", "/data.json", true, pv{"file": "data", "ext": "json"}, ""},
|
||||
{"/:file.:ext", "/data.tar.gz", true, pv{"file": "data", "ext": "tar.gz"}, ""},
|
||||
{"/:file.:ext", "/data", false, nil, ""},
|
||||
{"/:file.:ext", "/data.", false, nil, ""},
|
||||
{"/:file.:ext", "/.gitconfig", false, nil, ""},
|
||||
{"/:file.:ext", "/data.json/", false, nil, ""},
|
||||
{"/:file.:ext", "/data/json", false, nil, ""},
|
||||
{"/:file.:ext", "/data;json", false, nil, ""},
|
||||
{"/hello.:ext", "/hello.json", true, pv{"ext": "json"}, ""},
|
||||
{"/:file.json", "/hello.json", true, pv{"file": "hello"}, ""},
|
||||
{"/:file.json", "/hello.world.json", false, nil, ""},
|
||||
{"/file;:version", "/file;1", true, pv{"version": "1"}, ""},
|
||||
{"/file;:version", "/file,1", false, nil, ""},
|
||||
{"/file,:version", "/file,1", true, pv{"version": "1"}, ""},
|
||||
{"/file,:version", "/file;1", false, nil, ""},
|
||||
|
||||
{"/*", "/", true, nil, "/"},
|
||||
{"/*", "/hello", true, nil, "/hello"},
|
||||
{"/users/*", "/", false, nil, ""},
|
||||
{"/users/*", "/users", false, nil, ""},
|
||||
{"/users/*", "/users/", true, nil, "/"},
|
||||
{"/users/*", "/users/carl", true, nil, "/carl"},
|
||||
{"/users/*", "/profile/carl", false, nil, ""},
|
||||
{"/:name/*", "/carl", false, nil, ""},
|
||||
{"/:name/*", "/carl/", true, pv{"name": "carl"}, "/"},
|
||||
{"/:name/*", "/carl/photos", true, pv{"name": "carl"}, "/photos"},
|
||||
{"/:name/*", "/carl/photos%2f2015", true, pv{"name": "carl"}, "/photos%2f2015"},
|
||||
}
|
||||
|
||||
func TestPat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range PatTests {
|
||||
pat := New(test.pat)
|
||||
|
||||
if str := pat.String(); str != test.pat {
|
||||
t.Errorf("[%q %q] String()=%q, expected=%q", test.pat, test.req, str, test.pat)
|
||||
}
|
||||
|
||||
req := pat.Match(mustReq("GET", test.req))
|
||||
if (req != nil) != test.match {
|
||||
t.Errorf("[%q %q] match=%v, expected=%v", test.pat, test.req, req != nil, test.match)
|
||||
}
|
||||
if req == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
if path := pattern.Path(ctx); path != test.path {
|
||||
t.Errorf("[%q %q] path=%q, expected=%q", test.pat, test.req, path, test.path)
|
||||
}
|
||||
|
||||
vars := ctx.Value(pattern.AllVariables)
|
||||
if (vars != nil) != (test.vars != nil) {
|
||||
t.Errorf("[%q %q] vars=%#v, expected=%#v", test.pat, test.req, vars, test.vars)
|
||||
}
|
||||
if vars == nil {
|
||||
continue
|
||||
}
|
||||
if tvars := vars.(map[pattern.Variable]interface{}); !reflect.DeepEqual(tvars, test.vars) {
|
||||
t.Errorf("[%q %q] vars=%v, expected=%v", test.pat, test.req, tvars, test.vars)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadPathEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// This one is hard to fit into the table-driven test above since Go
|
||||
// refuses to have anything to do with poorly encoded URLs.
|
||||
ctx := pattern.SetPath(context.Background(), "/%nope")
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
if New("/:name").Match(r.WithContext(ctx)) != nil {
|
||||
t.Error("unexpected match")
|
||||
}
|
||||
}
|
||||
|
||||
var PathPrefixTests = []struct {
|
||||
pat string
|
||||
prefix string
|
||||
}{
|
||||
{"/", "/"},
|
||||
{"/hello/:world", "/hello/"},
|
||||
{"/users/:name/profile", "/users/"},
|
||||
{"/users/*", "/users/"},
|
||||
}
|
||||
|
||||
func TestPathPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range PathPrefixTests {
|
||||
pat := New(test.pat)
|
||||
if prefix := pat.PathPrefix(); prefix != test.prefix {
|
||||
t.Errorf("%q.PathPrefix() = %q, expected %q", test.pat, prefix, test.prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pat := New("/foo")
|
||||
if methods := pat.HTTPMethods(); methods != nil {
|
||||
t.Errorf("expected nil with no methods, got %v", methods)
|
||||
}
|
||||
|
||||
pat = Get("/boo")
|
||||
expect := map[string]struct{}{"GET": {}, "HEAD": {}}
|
||||
if methods := pat.HTTPMethods(); !reflect.DeepEqual(expect, methods) {
|
||||
t.Errorf("methods=%v, expected %v", methods, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pat := New("/hello/:name")
|
||||
req := pat.Match(mustReq("GET", "/hello/carl"))
|
||||
if req == nil {
|
||||
t.Fatal("expected a match")
|
||||
}
|
||||
if name := Param(req, "name"); name != "carl" {
|
||||
t.Errorf("name=%q, expected %q", name, "carl")
|
||||
}
|
||||
}
|
||||
70
vendor/goji.io/pat/url.go
generated
vendored
Normal file
70
vendor/goji.io/pat/url.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package pat
|
||||
|
||||
import "net/url"
|
||||
|
||||
// Stolen (with modifications) from net/url in the Go stdlib
|
||||
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func unescape(s string) (string, error) {
|
||||
// Count %, check that they're well-formed.
|
||||
n := 0
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
n++
|
||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||
s = s[i:]
|
||||
if len(s) > 3 {
|
||||
s = s[:3]
|
||||
}
|
||||
return "", url.EscapeError(s)
|
||||
}
|
||||
i += 3
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)-2*n)
|
||||
j := 0
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
|
||||
j++
|
||||
i += 3
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
i++
|
||||
}
|
||||
}
|
||||
return string(t), nil
|
||||
}
|
||||
55
vendor/goji.io/pat/url_test.go
generated
vendored
Normal file
55
vendor/goji.io/pat/url_test.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package pat
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var HexTexts = []struct {
|
||||
input byte
|
||||
ishex bool
|
||||
unhex byte
|
||||
}{
|
||||
{'0', true, 0},
|
||||
{'4', true, 4},
|
||||
{'a', true, 10},
|
||||
{'F', true, 15},
|
||||
{'h', false, 0},
|
||||
{'^', false, 0},
|
||||
}
|
||||
|
||||
func TestHex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range HexTexts {
|
||||
if actual := ishex(test.input); actual != test.ishex {
|
||||
t.Errorf("ishex(%v) == %v, expected %v", test.input, actual, test.ishex)
|
||||
}
|
||||
if actual := unhex(test.input); actual != test.unhex {
|
||||
t.Errorf("unhex(%v) == %v, expected %v", test.input, actual, test.unhex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var UnescapeTests = []struct {
|
||||
input string
|
||||
err error
|
||||
output string
|
||||
}{
|
||||
{"hello", nil, "hello"},
|
||||
{"file%20one%26two", nil, "file one&two"},
|
||||
{"one/two%2fthree", nil, "one/two/three"},
|
||||
{"this%20is%0not%valid", url.EscapeError("%0n"), ""},
|
||||
}
|
||||
|
||||
func TestUnescape(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range UnescapeTests {
|
||||
if actual, err := unescape(test.input); err != test.err {
|
||||
t.Errorf("unescape(%q) had err %v, expected %q", test.input, err, test.err)
|
||||
} else if actual != test.output {
|
||||
t.Errorf("unescape(%q) = %q, expected %q)", test.input, actual, test.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user