mirror of
https://github.com/openfaas/faas.git
synced 2025-06-20 04:56:38 +00:00
Merge master into breakout_swarm
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
This commit is contained in:
2
gateway/vendor/github.com/docker/distribution/registry/api/v2/descriptors.go
generated
vendored
2
gateway/vendor/github.com/docker/distribution/registry/api/v2/descriptors.go
generated
vendored
@ -4,9 +4,9 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
|
161
gateway/vendor/github.com/docker/distribution/registry/api/v2/headerparser_test.go
generated
vendored
Normal file
161
gateway/vendor/github.com/docker/distribution/registry/api/v2/headerparser_test.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseForwardedHeader(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
raw string
|
||||
expected map[string]string
|
||||
expectedRest string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
raw: "",
|
||||
},
|
||||
{
|
||||
name: "one pair",
|
||||
raw: " key = value ",
|
||||
expected: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
name: "two pairs",
|
||||
raw: " key1 = value1; key2=value2",
|
||||
expected: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
{
|
||||
name: "uppercase parameter",
|
||||
raw: "KeY=VaL",
|
||||
expected: map[string]string{"key": "VaL"},
|
||||
},
|
||||
{
|
||||
name: "missing key=value pair - be tolerant",
|
||||
raw: "key=val;",
|
||||
expected: map[string]string{"key": "val"},
|
||||
},
|
||||
{
|
||||
name: "quoted values",
|
||||
raw: `key="val";param = "[[ $((1 + 1)) == 3 ]] && echo panic!;" ; p=" abcd "`,
|
||||
expected: map[string]string{"key": "val", "param": "[[ $((1 + 1)) == 3 ]] && echo panic!;", "p": " abcd "},
|
||||
},
|
||||
{
|
||||
name: "empty quoted value",
|
||||
raw: `key=""`,
|
||||
expected: map[string]string{"key": ""},
|
||||
},
|
||||
{
|
||||
name: "quoted double quotes",
|
||||
raw: `key="\"value\""`,
|
||||
expected: map[string]string{"key": `"value"`},
|
||||
},
|
||||
{
|
||||
name: "quoted backslash",
|
||||
raw: `key="\"\\\""`,
|
||||
expected: map[string]string{"key": `"\"`},
|
||||
},
|
||||
{
|
||||
name: "ignore subsequent elements",
|
||||
raw: "key=a, param= b",
|
||||
expected: map[string]string{"key": "a"},
|
||||
expectedRest: " param= b",
|
||||
},
|
||||
{
|
||||
name: "empty element - be tolerant",
|
||||
raw: " , key=val",
|
||||
expectedRest: " key=val",
|
||||
},
|
||||
{
|
||||
name: "obscure key",
|
||||
raw: `ob₷C&r€ = value`,
|
||||
expected: map[string]string{`ob₷c&r€`: "value"},
|
||||
},
|
||||
{
|
||||
name: "duplicate parameter",
|
||||
raw: "key=a; p=b; key=c",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty parameter",
|
||||
raw: "=value",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty value",
|
||||
raw: "key= ",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty value before a new element ",
|
||||
raw: "key=,",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty value before a new pair",
|
||||
raw: "key=;",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "just parameter",
|
||||
raw: "key",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "missing key-value",
|
||||
raw: "a=b;;",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "unclosed quoted value",
|
||||
raw: `key="value`,
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "escaped terminating dquote",
|
||||
raw: `key="value\"`,
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "just a quoted value",
|
||||
raw: `"key=val"`,
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "quoted key",
|
||||
raw: `"key"=val`,
|
||||
expectedError: true,
|
||||
},
|
||||
} {
|
||||
parsed, rest, err := parseForwardedHeader(tc.raw)
|
||||
if err != nil && !tc.expectedError {
|
||||
t.Errorf("[%s] got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
if err == nil && tc.expectedError {
|
||||
t.Errorf("[%s] got unexpected non-error", tc.name)
|
||||
}
|
||||
if err != nil || tc.expectedError {
|
||||
continue
|
||||
}
|
||||
for key, value := range tc.expected {
|
||||
v, exists := parsed[key]
|
||||
if !exists {
|
||||
t.Errorf("[%s] missing expected parameter %q", tc.name, key)
|
||||
continue
|
||||
}
|
||||
if v != value {
|
||||
t.Errorf("[%s] got unexpected value for parameter %q: %q != %q", tc.name, key, v, value)
|
||||
}
|
||||
}
|
||||
for key, value := range parsed {
|
||||
if _, exists := tc.expected[key]; !exists {
|
||||
t.Errorf("[%s] got unexpected key/value pair: %q=%q", tc.name, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if rest != tc.expectedRest {
|
||||
t.Errorf("[%s] got unexpected unparsed string: %q != %q", tc.name, rest, tc.expectedRest)
|
||||
}
|
||||
}
|
||||
}
|
355
gateway/vendor/github.com/docker/distribution/registry/api/v2/routes_test.go
generated
vendored
Normal file
355
gateway/vendor/github.com/docker/distribution/registry/api/v2/routes_test.go
generated
vendored
Normal file
@ -0,0 +1,355 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type routeTestCase struct {
|
||||
RequestURI string
|
||||
ExpectedURI string
|
||||
Vars map[string]string
|
||||
RouteName string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// TestRouter registers a test handler with all the routes and ensures that
|
||||
// each route returns the expected path variables. Not method verification is
|
||||
// present. This not meant to be exhaustive but as check to ensure that the
|
||||
// expected variables are extracted.
|
||||
//
|
||||
// This may go away as the application structure comes together.
|
||||
func TestRouter(t *testing.T) {
|
||||
testCases := []routeTestCase{
|
||||
{
|
||||
RouteName: RouteNameBase,
|
||||
RequestURI: "/v2/",
|
||||
Vars: map[string]string{},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameManifest,
|
||||
RequestURI: "/v2/foo/manifests/bar",
|
||||
Vars: map[string]string{
|
||||
"name": "foo",
|
||||
"reference": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameManifest,
|
||||
RequestURI: "/v2/foo/bar/manifests/tag",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"reference": "tag",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameManifest,
|
||||
RequestURI: "/v2/foo/bar/manifests/sha256:abcdef01234567890",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"reference": "sha256:abcdef01234567890",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/foo/bar/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/docker.com/foo/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "docker.com/foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/docker.com/foo/bar/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "docker.com/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/docker.com/foo/bar/baz/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "docker.com/foo/bar/baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameBlob,
|
||||
RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"digest": "sha256:abcdef0919234",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameBlobUpload,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/uuid",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"uuid": "uuid",
|
||||
},
|
||||
},
|
||||
{
|
||||
// support uuid proper
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
|
||||
},
|
||||
},
|
||||
{
|
||||
// supports urlsafe base64
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA_-==",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar",
|
||||
"uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA_-==",
|
||||
},
|
||||
},
|
||||
{
|
||||
// does not match
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/bar/blobs/uploads/totalandcompletejunk++$$-==",
|
||||
StatusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
// Check ambiguity: ensure we can distinguish between tags for
|
||||
// "foo/bar/image/image" and image for "foo/bar/image" with tag
|
||||
// "tags"
|
||||
RouteName: RouteNameManifest,
|
||||
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar/manifests",
|
||||
"reference": "tags",
|
||||
},
|
||||
},
|
||||
{
|
||||
// This case presents an ambiguity between foo/bar with tag="tags"
|
||||
// and list tags for "foo/bar/manifest"
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/foo/bar/manifests/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "foo/bar/manifests",
|
||||
},
|
||||
},
|
||||
{
|
||||
RouteName: RouteNameManifest,
|
||||
RequestURI: "/v2/locahost:8080/foo/bar/baz/manifests/tag",
|
||||
Vars: map[string]string{
|
||||
"name": "locahost:8080/foo/bar/baz",
|
||||
"reference": "tag",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
checkTestRouter(t, testCases, "", true)
|
||||
checkTestRouter(t, testCases, "/prefix/", true)
|
||||
}
|
||||
|
||||
func TestRouterWithPathTraversals(t *testing.T) {
|
||||
testCases := []routeTestCase{
|
||||
{
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||
ExpectedURI: "/blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||
StatusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
// Testing for path traversal attack handling
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/foo/../bar/baz/tags/list",
|
||||
ExpectedURI: "/v2/bar/baz/tags/list",
|
||||
Vars: map[string]string{
|
||||
"name": "bar/baz",
|
||||
},
|
||||
},
|
||||
}
|
||||
checkTestRouter(t, testCases, "", false)
|
||||
}
|
||||
|
||||
func TestRouterWithBadCharacters(t *testing.T) {
|
||||
if testing.Short() {
|
||||
testCases := []routeTestCase{
|
||||
{
|
||||
RouteName: RouteNameBlobUploadChunk,
|
||||
RequestURI: "/v2/foo/blob/uploads/不95306FA-FAD3-4E36-8D41-CF1C93EF8286",
|
||||
StatusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
// Testing for path traversal attack handling
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: "/v2/foo/不bar/tags/list",
|
||||
StatusCode: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
checkTestRouter(t, testCases, "", true)
|
||||
} else {
|
||||
// in the long version we're going to fuzz the router
|
||||
// with random UTF8 characters not in the 128 bit ASCII range.
|
||||
// These are not valid characters for the router and we expect
|
||||
// 404s on every test.
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
testCases := make([]routeTestCase, 1000)
|
||||
for idx := range testCases {
|
||||
testCases[idx] = routeTestCase{
|
||||
RouteName: RouteNameTags,
|
||||
RequestURI: fmt.Sprintf("/v2/%v/%v/tags/list", randomString(10), randomString(10)),
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
checkTestRouter(t, testCases, "", true)
|
||||
}
|
||||
}
|
||||
|
||||
func checkTestRouter(t *testing.T, testCases []routeTestCase, prefix string, deeplyEqual bool) {
|
||||
router := RouterWithPrefix(prefix)
|
||||
|
||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
testCase := routeTestCase{
|
||||
RequestURI: r.RequestURI,
|
||||
Vars: mux.Vars(r),
|
||||
RouteName: mux.CurrentRoute(r).GetName(),
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
|
||||
if err := enc.Encode(testCase); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Startup test server
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
for _, testcase := range testCases {
|
||||
testcase.RequestURI = strings.TrimSuffix(prefix, "/") + testcase.RequestURI
|
||||
// Register the endpoint
|
||||
route := router.GetRoute(testcase.RouteName)
|
||||
if route == nil {
|
||||
t.Fatalf("route for name %q not found", testcase.RouteName)
|
||||
}
|
||||
|
||||
route.Handler(testHandler)
|
||||
|
||||
u := server.URL + testcase.RequestURI
|
||||
|
||||
resp, err := http.Get(u)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error issuing get request: %v", err)
|
||||
}
|
||||
|
||||
if testcase.StatusCode == 0 {
|
||||
// Override default, zero-value
|
||||
testcase.StatusCode = http.StatusOK
|
||||
}
|
||||
if testcase.ExpectedURI == "" {
|
||||
// Override default, zero-value
|
||||
testcase.ExpectedURI = testcase.RequestURI
|
||||
}
|
||||
|
||||
if resp.StatusCode != testcase.StatusCode {
|
||||
t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode)
|
||||
}
|
||||
|
||||
if testcase.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
// We don't care about json response.
|
||||
continue
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
|
||||
var actualRouteInfo routeTestCase
|
||||
if err := dec.Decode(&actualRouteInfo); err != nil {
|
||||
t.Fatalf("error reading json response: %v", err)
|
||||
}
|
||||
// Needs to be set out of band
|
||||
actualRouteInfo.StatusCode = resp.StatusCode
|
||||
|
||||
if actualRouteInfo.RequestURI != testcase.ExpectedURI {
|
||||
t.Fatalf("URI %v incorrectly parsed, expected %v", actualRouteInfo.RequestURI, testcase.ExpectedURI)
|
||||
}
|
||||
|
||||
if actualRouteInfo.RouteName != testcase.RouteName {
|
||||
t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName)
|
||||
}
|
||||
|
||||
// when testing deep equality, the actualRouteInfo has an empty ExpectedURI, we don't want
|
||||
// that to make the comparison fail. We're otherwise done with the testcase so empty the
|
||||
// testcase.ExpectedURI
|
||||
testcase.ExpectedURI = ""
|
||||
if deeplyEqual && !reflect.DeepEqual(actualRouteInfo, testcase) {
|
||||
t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase)
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------- START LICENSED CODE --------------
|
||||
// The following code is derivative of https://github.com/google/gofuzz
|
||||
// gofuzz is licensed under the Apache License, Version 2.0, January 2004,
|
||||
// a copy of which can be found in the LICENSE file at the root of this
|
||||
// repository.
|
||||
|
||||
// These functions allow us to generate strings containing only multibyte
|
||||
// characters that are invalid in our URLs. They are used above for fuzzing
|
||||
// to ensure we always get 404s on these invalid strings
|
||||
type charRange struct {
|
||||
first, last rune
|
||||
}
|
||||
|
||||
// choose returns a random unicode character from the given range, using the
|
||||
// given randomness source.
|
||||
func (r *charRange) choose() rune {
|
||||
count := int64(r.last - r.first)
|
||||
return r.first + rune(rand.Int63n(count))
|
||||
}
|
||||
|
||||
var unicodeRanges = []charRange{
|
||||
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
|
||||
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
runes := make([]rune, length)
|
||||
for i := range runes {
|
||||
runes[i] = unicodeRanges[rand.Intn(len(unicodeRanges))].choose()
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// -------------- END LICENSED CODE --------------
|
3
gateway/vendor/github.com/docker/distribution/registry/api/v2/urls.go
generated
vendored
3
gateway/vendor/github.com/docker/distribution/registry/api/v2/urls.go
generated
vendored
@ -1,7 +1,6 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -150,8 +149,6 @@ func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) {
|
||||
tagOrDigest = v.Tag()
|
||||
case reference.Digested:
|
||||
tagOrDigest = v.Digest().String()
|
||||
default:
|
||||
return "", fmt.Errorf("reference must have a tag or digest")
|
||||
}
|
||||
|
||||
manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest)
|
||||
|
484
gateway/vendor/github.com/docker/distribution/registry/api/v2/urls_test.go
generated
vendored
Normal file
484
gateway/vendor/github.com/docker/distribution/registry/api/v2/urls_test.go
generated
vendored
Normal file
@ -0,0 +1,484 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
type urlBuilderTestCase struct {
|
||||
description string
|
||||
expectedPath string
|
||||
build func() (string, error)
|
||||
}
|
||||
|
||||
func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase {
|
||||
fooBarRef, _ := reference.ParseNamed("foo/bar")
|
||||
return []urlBuilderTestCase{
|
||||
{
|
||||
description: "test base url",
|
||||
expectedPath: "/v2/",
|
||||
build: urlBuilder.BuildBaseURL,
|
||||
},
|
||||
{
|
||||
description: "test tags url",
|
||||
expectedPath: "/v2/foo/bar/tags/list",
|
||||
build: func() (string, error) {
|
||||
return urlBuilder.BuildTagsURL(fooBarRef)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "test manifest url",
|
||||
expectedPath: "/v2/foo/bar/manifests/tag",
|
||||
build: func() (string, error) {
|
||||
ref, _ := reference.WithTag(fooBarRef, "tag")
|
||||
return urlBuilder.BuildManifestURL(ref)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "build blob url",
|
||||
expectedPath: "/v2/foo/bar/blobs/sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5",
|
||||
build: func() (string, error) {
|
||||
ref, _ := reference.WithDigest(fooBarRef, "sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5")
|
||||
return urlBuilder.BuildBlobURL(ref)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "build blob upload url",
|
||||
expectedPath: "/v2/foo/bar/blobs/uploads/",
|
||||
build: func() (string, error) {
|
||||
return urlBuilder.BuildBlobUploadURL(fooBarRef)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "build blob upload url with digest and size",
|
||||
expectedPath: "/v2/foo/bar/blobs/uploads/?digest=sha256%3A3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5&size=10000",
|
||||
build: func() (string, error) {
|
||||
return urlBuilder.BuildBlobUploadURL(fooBarRef, url.Values{
|
||||
"size": []string{"10000"},
|
||||
"digest": []string{"sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5"},
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "build blob upload chunk url",
|
||||
expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part",
|
||||
build: func() (string, error) {
|
||||
return urlBuilder.BuildBlobUploadChunkURL(fooBarRef, "uuid-part")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "build blob upload chunk url with digest and size",
|
||||
expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=sha256%3A3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5&size=10000",
|
||||
build: func() (string, error) {
|
||||
return urlBuilder.BuildBlobUploadChunkURL(fooBarRef, "uuid-part", url.Values{
|
||||
"size": []string{"10000"},
|
||||
"digest": []string{"sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5"},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestURLBuilder tests the various url building functions, ensuring they are
|
||||
// returning the expected values.
|
||||
func TestURLBuilder(t *testing.T) {
|
||||
roots := []string{
|
||||
"http://example.com",
|
||||
"https://example.com",
|
||||
"http://localhost:5000",
|
||||
"https://localhost:5443",
|
||||
}
|
||||
|
||||
doTest := func(relative bool) {
|
||||
for _, root := range roots {
|
||||
urlBuilder, err := NewURLBuilderFromString(root, relative)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||
}
|
||||
|
||||
for _, testCase := range makeURLBuilderTestCases(urlBuilder) {
|
||||
url, err := testCase.build()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||
}
|
||||
expectedURL := testCase.expectedPath
|
||||
if !relative {
|
||||
expectedURL = root + expectedURL
|
||||
}
|
||||
|
||||
if url != expectedURL {
|
||||
t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doTest(true)
|
||||
doTest(false)
|
||||
}
|
||||
|
||||
func TestURLBuilderWithPrefix(t *testing.T) {
|
||||
roots := []string{
|
||||
"http://example.com/prefix/",
|
||||
"https://example.com/prefix/",
|
||||
"http://localhost:5000/prefix/",
|
||||
"https://localhost:5443/prefix/",
|
||||
}
|
||||
|
||||
doTest := func(relative bool) {
|
||||
for _, root := range roots {
|
||||
urlBuilder, err := NewURLBuilderFromString(root, relative)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating urlbuilder: %v", err)
|
||||
}
|
||||
|
||||
for _, testCase := range makeURLBuilderTestCases(urlBuilder) {
|
||||
url, err := testCase.build()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||
}
|
||||
|
||||
expectedURL := testCase.expectedPath
|
||||
if !relative {
|
||||
expectedURL = root[0:len(root)-1] + expectedURL
|
||||
}
|
||||
if url != expectedURL {
|
||||
t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doTest(true)
|
||||
doTest(false)
|
||||
}
|
||||
|
||||
type builderFromRequestTestCase struct {
|
||||
request *http.Request
|
||||
base string
|
||||
}
|
||||
|
||||
func TestBuilderFromRequest(t *testing.T) {
|
||||
u, err := url.Parse("http://example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testRequests := []struct {
|
||||
name string
|
||||
request *http.Request
|
||||
base string
|
||||
configHost url.URL
|
||||
}{
|
||||
{
|
||||
name: "no forwarded header",
|
||||
request: &http.Request{URL: u, Host: u.Host},
|
||||
base: "http://example.com",
|
||||
},
|
||||
{
|
||||
name: "https protocol forwarded with a non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Custom-Forwarded-Proto": []string{"https"},
|
||||
}},
|
||||
base: "http://example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded protocol is the same",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{"https"},
|
||||
}},
|
||||
base: "https://example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded host with a non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com"},
|
||||
}},
|
||||
base: "http://first.example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded multiple hosts a with non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com, proxy1.example.com"},
|
||||
}},
|
||||
base: "http://first.example.com",
|
||||
},
|
||||
{
|
||||
name: "host configured in config file takes priority",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com, proxy1.example.com"},
|
||||
}},
|
||||
base: "https://third.example.com:5000",
|
||||
configHost: url.URL{
|
||||
Scheme: "https",
|
||||
Host: "third.example.com:5000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "forwarded host and port with just one non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com:443"},
|
||||
}},
|
||||
base: "http://first.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "forwarded port with a non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"example.com:5000"},
|
||||
"X-Forwarded-Port": []string{"5000"},
|
||||
}},
|
||||
base: "http://example.com:5000",
|
||||
},
|
||||
{
|
||||
name: "forwarded multiple ports with a non-standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Port": []string{"443 , 5001"},
|
||||
}},
|
||||
base: "http://example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded standard port with non-standard headers",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{"https"},
|
||||
"X-Forwarded-Host": []string{"example.com"},
|
||||
"X-Forwarded-Port": []string{"443"},
|
||||
}},
|
||||
base: "https://example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded standard port with non-standard headers and explicit port",
|
||||
request: &http.Request{URL: u, Host: u.Host + ":443", Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{"https"},
|
||||
"X-Forwarded-Host": []string{u.Host + ":443"},
|
||||
"X-Forwarded-Port": []string{"443"},
|
||||
}},
|
||||
base: "https://example.com:443",
|
||||
},
|
||||
{
|
||||
name: "several non-standard headers",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{"https"},
|
||||
"X-Forwarded-Host": []string{" first.example.com:12345 "},
|
||||
}},
|
||||
base: "https://first.example.com:12345",
|
||||
},
|
||||
{
|
||||
name: "forwarded host with port supplied takes priority",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com:5000"},
|
||||
"X-Forwarded-Port": []string{"80"},
|
||||
}},
|
||||
base: "http://first.example.com:5000",
|
||||
},
|
||||
{
|
||||
name: "malformed forwarded port",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Host": []string{"first.example.com"},
|
||||
"X-Forwarded-Port": []string{"abcd"},
|
||||
}},
|
||||
base: "http://first.example.com",
|
||||
},
|
||||
{
|
||||
name: "forwarded protocol and addr using standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`proto=https;host="192.168.22.30:80"`},
|
||||
}},
|
||||
base: "https://192.168.22.30:80",
|
||||
},
|
||||
{
|
||||
name: "forwarded host takes priority over for",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`host="reg.example.com:5000";for="192.168.22.30"`},
|
||||
}},
|
||||
base: "http://reg.example.com:5000",
|
||||
},
|
||||
{
|
||||
name: "forwarded host and protocol using standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`host=reg.example.com;proto=https`},
|
||||
}},
|
||||
base: "https://reg.example.com",
|
||||
},
|
||||
{
|
||||
name: "process just the first standard forwarded header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`host="reg.example.com:88";proto=http`, `host=reg.example.com;proto=https`},
|
||||
}},
|
||||
base: "http://reg.example.com:88",
|
||||
},
|
||||
{
|
||||
name: "process just the first list element of standard header",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`host="reg.example.com:443";proto=https, host="reg.example.com:80";proto=http`},
|
||||
}},
|
||||
base: "https://reg.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "IPv6 address use host",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`for="2607:f0d0:1002:51::4";host="[2607:f0d0:1002:51::4]:5001"`},
|
||||
"X-Forwarded-Port": []string{"5002"},
|
||||
}},
|
||||
base: "http://[2607:f0d0:1002:51::4]:5001",
|
||||
},
|
||||
{
|
||||
name: "IPv6 address with port",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"Forwarded": []string{`host="[2607:f0d0:1002:51::4]:4000"`},
|
||||
"X-Forwarded-Port": []string{"5001"},
|
||||
}},
|
||||
base: "http://[2607:f0d0:1002:51::4]:4000",
|
||||
},
|
||||
{
|
||||
name: "non-standard and standard forward headers",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{`https`},
|
||||
"X-Forwarded-Host": []string{`first.example.com`},
|
||||
"X-Forwarded-Port": []string{``},
|
||||
"Forwarded": []string{`host=first.example.com; proto=https`},
|
||||
}},
|
||||
base: "https://first.example.com",
|
||||
},
|
||||
{
|
||||
name: "standard header takes precedence over non-standard headers",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{`http`},
|
||||
"Forwarded": []string{`host=second.example.com; proto=https`},
|
||||
"X-Forwarded-Host": []string{`first.example.com`},
|
||||
"X-Forwarded-Port": []string{`4000`},
|
||||
}},
|
||||
base: "https://second.example.com",
|
||||
},
|
||||
{
|
||||
name: "incomplete standard header uses default",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{`https`},
|
||||
"Forwarded": []string{`for=127.0.0.1`},
|
||||
"X-Forwarded-Host": []string{`first.example.com`},
|
||||
"X-Forwarded-Port": []string{`4000`},
|
||||
}},
|
||||
base: "http://" + u.Host,
|
||||
},
|
||||
{
|
||||
name: "standard with just proto",
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: http.Header{
|
||||
"X-Forwarded-Proto": []string{`https`},
|
||||
"Forwarded": []string{`proto=https`},
|
||||
"X-Forwarded-Host": []string{`first.example.com`},
|
||||
"X-Forwarded-Port": []string{`4000`},
|
||||
}},
|
||||
base: "https://" + u.Host,
|
||||
},
|
||||
}
|
||||
|
||||
doTest := func(relative bool) {
|
||||
for _, tr := range testRequests {
|
||||
var builder *URLBuilder
|
||||
if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
|
||||
builder = NewURLBuilder(&tr.configHost, relative)
|
||||
} else {
|
||||
builder = NewURLBuilderFromRequest(tr.request, relative)
|
||||
}
|
||||
|
||||
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||
buildURL, err := testCase.build()
|
||||
if err != nil {
|
||||
t.Fatalf("[relative=%t, request=%q, case=%q]: error building url: %v", relative, tr.name, testCase.description, err)
|
||||
}
|
||||
|
||||
expectedURL := testCase.expectedPath
|
||||
if !relative {
|
||||
expectedURL = tr.base + expectedURL
|
||||
}
|
||||
|
||||
if buildURL != expectedURL {
|
||||
t.Errorf("[relative=%t, request=%q, case=%q]: %q != %q", relative, tr.name, testCase.description, buildURL, expectedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doTest(true)
|
||||
doTest(false)
|
||||
}
|
||||
|
||||
func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
||||
u, err := url.Parse("http://example.com/prefix/v2/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
forwardedProtoHeader := make(http.Header, 1)
|
||||
forwardedProtoHeader.Set("X-Forwarded-Proto", "https")
|
||||
|
||||
testRequests := []struct {
|
||||
request *http.Request
|
||||
base string
|
||||
configHost url.URL
|
||||
}{
|
||||
{
|
||||
request: &http.Request{URL: u, Host: u.Host},
|
||||
base: "http://example.com/prefix/",
|
||||
},
|
||||
|
||||
{
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||
base: "http://example.com/prefix/",
|
||||
},
|
||||
{
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||
base: "https://example.com/prefix/",
|
||||
},
|
||||
{
|
||||
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||
base: "https://subdomain.example.com/prefix/",
|
||||
configHost: url.URL{
|
||||
Scheme: "https",
|
||||
Host: "subdomain.example.com",
|
||||
Path: "/prefix/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var relative bool
|
||||
for _, tr := range testRequests {
|
||||
var builder *URLBuilder
|
||||
if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
|
||||
builder = NewURLBuilder(&tr.configHost, false)
|
||||
} else {
|
||||
builder = NewURLBuilderFromRequest(tr.request, false)
|
||||
}
|
||||
|
||||
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||
buildURL, err := testCase.build()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error building url: %v", testCase.description, err)
|
||||
}
|
||||
|
||||
var expectedURL string
|
||||
proto, ok := tr.request.Header["X-Forwarded-Proto"]
|
||||
if !ok {
|
||||
expectedURL = testCase.expectedPath
|
||||
if !relative {
|
||||
expectedURL = tr.base[0:len(tr.base)-1] + expectedURL
|
||||
}
|
||||
} else {
|
||||
urlBase, err := url.Parse(tr.base)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
urlBase.Scheme = proto[0]
|
||||
expectedURL = testCase.expectedPath
|
||||
if !relative {
|
||||
expectedURL = urlBase.String()[0:len(urlBase.String())-1] + expectedURL
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if buildURL != expectedURL {
|
||||
t.Fatalf("%s: %q != %q", testCase.description, buildURL, expectedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user