可靠性: 完善 R2 备份恢复与对象生命周期
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user