Files
tamabox/internal/storage/cdn_url_test.go
T

113 lines
4.9 KiB
Go

package storage
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestObjectKeyFromCDNURL(t *testing.T) {
tests := []struct {
name string
rawURL string
cdnBase string
wantKey string
owned bool
wantErr bool
}{
{
name: "scheme less base", rawURL: "https://cdn.example.com/2026/11/pic.webp",
cdnBase: "cdn.example.com", wantKey: "2026/11/pic.webp", owned: true,
},
{
name: "base path", rawURL: "https://cdn.example.com/assets/2026/pic.webp",
cdnBase: "https://cdn.example.com/assets", wantKey: "2026/pic.webp", owned: true,
},
{
name: "base trailing slash and escaped space", rawURL: "https://cdn.example.com/tamabox/a%20b.webp",
cdnBase: "cdn.example.com/tamabox/", wantKey: "a b.webp", owned: true,
},
{
name: "host case insensitive", rawURL: "https://CDN.EXAMPLE.COM/a.webp",
cdnBase: "cdn.example.com", wantKey: "a.webp", owned: true,
},
{
name: "preserve bucket prefix", rawURL: "https://cdn.example.com/tamabox/2026/a.webp",
cdnBase: "cdn.example.com", wantKey: "tamabox/2026/a.webp", owned: true,
},
{
name: "external host not owned", rawURL: "https://outside.example.com/a.webp",
cdnBase: "cdn.example.com", owned: false,
},
{
name: "host suffix attack not owned", rawURL: "https://cdn.example.com.evil.test/a.webp",
cdnBase: "cdn.example.com", owned: false,
},
{
name: "base path complete segment", rawURL: "https://cdn.example.com/assets2/a.webp",
cdnBase: "cdn.example.com/assets", owned: false,
},
{
name: "base path case sensitive", rawURL: "https://cdn.example.com/Assets/a.webp",
cdnBase: "cdn.example.com/assets", owned: false,
},
{name: "HTTP rejected", rawURL: "http://cdn.example.com/a", cdnBase: "cdn.example.com", wantErr: true},
{name: "relative rejected", rawURL: "/a", cdnBase: "cdn.example.com", wantErr: true},
{name: "scheme relative rejected", rawURL: "//cdn.example.com/a", cdnBase: "cdn.example.com", wantErr: true},
{name: "userinfo rejected", rawURL: "https://[email protected]/a", cdnBase: "cdn.example.com", wantErr: true},
{name: "query rejected", rawURL: "https://cdn.example.com/a?x=1", cdnBase: "cdn.example.com", wantErr: true},
{name: "empty query rejected", rawURL: "https://cdn.example.com/a?", cdnBase: "cdn.example.com", wantErr: true},
{name: "fragment rejected", rawURL: "https://cdn.example.com/a#fragment", cdnBase: "cdn.example.com", wantErr: true},
{name: "empty fragment rejected", rawURL: "https://cdn.example.com/a#", cdnBase: "cdn.example.com", wantErr: true},
{name: "backslash rejected", rawURL: `https://cdn.example.com/a\b`, cdnBase: "cdn.example.com", wantErr: true},
{name: "encoded backslash rejected", rawURL: "https://cdn.example.com/a%5Cb", cdnBase: "cdn.example.com", wantErr: true},
{name: "encoded slash rejected", rawURL: "https://cdn.example.com/a%2Fb", cdnBase: "cdn.example.com", wantErr: true},
{name: "control rejected", rawURL: "https://cdn.example.com/a\x7fb", cdnBase: "cdn.example.com", wantErr: true},
{name: "encoded control rejected", rawURL: "https://cdn.example.com/a%0Ab", cdnBase: "cdn.example.com", wantErr: true},
{name: "dot rejected", rawURL: "https://cdn.example.com/a/./b", cdnBase: "cdn.example.com", wantErr: true},
{name: "dot dot rejected", rawURL: "https://cdn.example.com/a/../b", cdnBase: "cdn.example.com", wantErr: true},
{name: "encoded dot dot rejected", rawURL: "https://cdn.example.com/a/%2E%2E/b", cdnBase: "cdn.example.com", wantErr: true},
{name: "empty path rejected", rawURL: "https://cdn.example.com", cdnBase: "cdn.example.com", wantErr: true},
{name: "root path rejected", rawURL: "https://cdn.example.com/", cdnBase: "cdn.example.com", wantErr: true},
{name: "empty middle segment rejected", rawURL: "https://cdn.example.com/a//b", cdnBase: "cdn.example.com", wantErr: true},
{name: "empty last segment rejected", rawURL: "https://cdn.example.com/a/", cdnBase: "cdn.example.com", wantErr: true},
{name: "missing key below base rejected", rawURL: "https://cdn.example.com/assets", cdnBase: "cdn.example.com/assets", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key, owned, err := ObjectKeyFromCDNURL(tt.rawURL, tt.cdnBase)
if tt.wantErr {
require.Error(t, err)
assert.False(t, owned)
assert.Empty(t, key)
return
}
require.NoError(t, err)
assert.Equal(t, tt.owned, owned)
assert.Equal(t, tt.wantKey, key)
})
}
}
func TestObjectKeyFromCDNURLRejectsInvalidBase(t *testing.T) {
for _, base := range []string{
"",
"http://cdn.example.com",
"cdn.example.com?x=1",
"cdn.example.com#fragment",
"cdn.example.com/assets/../other",
"cdn.example.com/assets//other",
"cdn.example.com/assets%2Fother",
"[email protected]",
} {
t.Run(base, func(t *testing.T) {
key, owned, err := ObjectKeyFromCDNURL("https://cdn.example.com/assets/a", base)
require.Error(t, err)
assert.False(t, owned)
assert.Empty(t, key)
})
}
}