92 lines
3.0 KiB
Go
92 lines
3.0 KiB
Go
package assets
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type assetRoundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (function assetRoundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
return function(request)
|
|
}
|
|
|
|
type remotePolicyFunc func(context.Context, *url.URL) error
|
|
|
|
func (function remotePolicyFunc) Validate(ctx context.Context, target *url.URL) error {
|
|
return function(ctx, target)
|
|
}
|
|
|
|
func TestHTTPRemoteFetcherRestrictsProtocolAndSize(t *testing.T) {
|
|
fetcher := NewHTTPRemoteFetcher(http.DefaultClient, 4)
|
|
if _, err := fetcher.Fetch(context.Background(), "file:///etc/passwd"); !errors.Is(err, ErrRemoteProtocol) {
|
|
t.Fatalf("file protocol error = %v", err)
|
|
}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "image/png")
|
|
_, _ = io.WriteString(w, "12345")
|
|
}))
|
|
defer server.Close()
|
|
if _, err := fetcher.Fetch(context.Background(), server.URL); !errors.Is(err, ErrRemoteTooLarge) {
|
|
t.Fatalf("oversize error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHTTPRemoteFetcherReturnsBoundedBody(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "image/png")
|
|
_, _ = io.WriteString(w, "1234")
|
|
}))
|
|
defer server.Close()
|
|
blob, err := NewHTTPRemoteFetcher(http.DefaultClient, 4).Fetch(context.Background(), server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer blob.Body.Close()
|
|
body, _ := io.ReadAll(blob.Body)
|
|
if string(body) != "1234" || blob.Size != 4 || blob.ContentType != "image/png" {
|
|
t.Fatalf("blob = %#v body=%q", blob, body)
|
|
}
|
|
}
|
|
|
|
func TestPolicyHTTPRemoteFetcherValidatesInitialAndRedirectDestinations(t *testing.T) {
|
|
validated := make([]string, 0, 2)
|
|
policy := remotePolicyFunc(func(_ context.Context, target *url.URL) error {
|
|
validated = append(validated, target.Hostname())
|
|
if target.Hostname() == "private.test" {
|
|
return errors.New("private destination")
|
|
}
|
|
return nil
|
|
})
|
|
client := &http.Client{Timeout: time.Second, Transport: assetRoundTripFunc(func(request *http.Request) (*http.Response, error) {
|
|
if request.URL.Hostname() == "public.test" {
|
|
return &http.Response{StatusCode: http.StatusFound, Header: http.Header{"Location": []string{"http://private.test/asset"}}, Body: io.NopCloser(strings.NewReader("")), Request: request}, nil
|
|
}
|
|
t.Fatal("redirect target transport must not run")
|
|
return nil, nil
|
|
})}
|
|
fetcher, err := NewPolicyHTTPRemoteFetcher(client, 4, policy)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := fetcher.Fetch(context.Background(), "https://public.test/asset"); err == nil {
|
|
t.Fatal("policy-denied redirect was accepted")
|
|
}
|
|
if strings.Join(validated, ",") != "public.test,private.test" {
|
|
t.Fatalf("validated destinations = %v", validated)
|
|
}
|
|
}
|
|
|
|
func TestNewPublicHTTPRemoteFetcherRequiresPolicy(t *testing.T) {
|
|
if _, err := NewPublicHTTPRemoteFetcher(time.Second, 4, nil); err == nil {
|
|
t.Fatal("nil public destination policy accepted")
|
|
}
|
|
}
|