Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
package hevcpull
import (
"errors"
"net/url"
"testing"
"time"
)
func TestSignAndVerifyURL(t *testing.T) {
now := time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC)
signed, err := SignURL(
"https://app.example.com/api/app/vid/transcode/m3u8/laosiji/m3m/demo.m3u8",
"test-pull-secret-strong-32-bytes!!",
now.Add(480*time.Hour),
)
if err != nil {
t.Fatalf("SignURL failed: %v", err)
}
parsed, err := url.Parse(signed)
if err != nil {
t.Fatalf("parse signed URL: %v", err)
}
if err = VerifyURL(parsed, "test-pull-secret-strong-32-bytes!!", now); err != nil {
t.Fatalf("VerifyURL failed: %v", err)
}
parsed.Path += ".tampered"
if err = VerifyURL(parsed, "test-pull-secret-strong-32-bytes!!", now); !errors.Is(err, ErrInvalidSignature) {
t.Fatalf("tampered path returned %v", err)
}
}
func TestVerifyURLRejectsExpiredOrExtraQuery(t *testing.T) {
now := time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC)
signed, err := SignURL(
"https://app.example.com/api/app/vid/transcode/m3u8/source.m3u8",
"test-pull-secret-strong-32-bytes!!",
now.Add(time.Minute),
)
if err != nil {
t.Fatalf("SignURL failed: %v", err)
}
parsed, _ := url.Parse(signed)
if err = VerifyURL(parsed, "test-pull-secret-strong-32-bytes!!", now.Add(time.Minute)); !errors.Is(err, ErrExpired) {
t.Fatalf("expired URL returned %v", err)
}
parsed, _ = url.Parse(signed)
query := parsed.Query()
query.Set("c", "unbound-cdn")
parsed.RawQuery = query.Encode()
if err = VerifyURL(parsed, "test-pull-secret-strong-32-bytes!!", now); !errors.Is(err, ErrInvalidSignature) {
t.Fatalf("extra query returned %v", err)
}
}
func TestSignURLRejectsWeakSecret(t *testing.T) {
_, err := SignURL("https://app.example.com/source.m3u8", "short", time.Now().Add(time.Hour))
if !errors.Is(err, ErrInvalidSecret) {
t.Fatalf("weak secret returned %v", err)
}
}
func TestRedactSignedPullURL(t *testing.T) {
const signature = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
raw := "https://app.example/source.m3u8?hevc_exp=1&hevc_sig=" + signature
redacted := RedactURL(raw)
if redacted == raw || RedactText(redacted) != redacted {
t.Fatalf("URL was not redacted: %s", redacted)
}
if got := RedactText("file_url=" + url.QueryEscape(raw)); got == "file_url="+url.QueryEscape(raw) {
t.Fatalf("escaped nested URL was not redacted: %s", got)
}
}
func TestNormalizeSource(t *testing.T) {
tests := []struct {
source string
want string
}{
{source: " /laosiji/m3m/demo.m3u8 ", want: "laosiji/m3m/demo.m3u8"},
{source: "sp/movie/index.m3u8", want: "sp/movie/index.m3u8"},
{source: "sp/movie/../index.m3u8"},
{source: `sp\movie\index.m3u8`},
{source: `sp/movie%5Cindex.m3u8`},
{source: `sp/movie%0Aindex.m3u8`},
{source: "https://cdn.example.com/index.m3u8"},
{source: "sp/movie/index.m3u8?token=x"},
{source: "sp/movie/index.mp4"},
{source: "sp/movie/index.M3U8"},
}
for _, tt := range tests {
got, err := NormalizeSource(tt.source)
if tt.want == "" {
if err == nil {
t.Errorf("NormalizeSource(%q) = %q, want error", tt.source, got)
}
continue
}
if err != nil || got != tt.want {
t.Errorf("NormalizeSource(%q) = %q, %v; want %q", tt.source, got, err, tt.want)
}
}
}
func TestResolveChildSource(t *testing.T) {
tests := []struct {
name string
parent string
child string
want string
}{
{
name: "relative child",
parent: "sp/movie/master.m3u8",
child: "720/index.m3u8",
want: "sp/movie/720/index.m3u8",
},
{
name: "parent traversal",
parent: "sp/movie/master.m3u8",
child: "../audio/index.m3u8",
want: "sp/audio/index.m3u8",
},
{
name: "root child remains on default SP origin",
parent: "sp/movie/master.m3u8",
child: "/shared/index.m3u8",
want: "shared/index.m3u8",
},
{
name: "explicit PMS root namespace is preserved",
parent: "pms/movie/master.m3u8",
child: "/pms/shared/index.m3u8",
want: "pms/shared/index.m3u8",
},
{
name: "explicit laosiji root namespace is preserved",
parent: "laosiji/m3m/movie/master.m3u8",
child: "/laosiji/shared/index.m3u8",
want: "laosiji/shared/index.m3u8",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ResolveChildSource(tt.parent, tt.child)
if err != nil {
t.Fatalf("ResolveChildSource error: %v", err)
}
if got != tt.want {
t.Fatalf("ResolveChildSource = %q, want %q", got, tt.want)
}
})
}
for _, tt := range []struct {
parent string
child string
}{
{parent: "sp/movie/master.m3u8", child: "https://cdn.example.com/index.m3u8"},
{parent: "sp/movie/master.m3u8", child: "index.m3u8?token=secret"},
{parent: "sp/movie/master.m3u8", child: "index.ts"},
{parent: "sp/movie/master.m3u8", child: `..\index.m3u8`},
{parent: "sp/movie/master.m3u8", child: "../../outside/index.m3u8"},
{parent: "sp/movie/master.m3u8", child: "../../../sp/outside/index.m3u8"},
{parent: "sp/movie/master.m3u8", child: "/pms/outside/index.m3u8"},
{parent: "pms/movie/master.m3u8", child: "/shared/index.m3u8"},
{parent: "pms/movie/master.m3u8", child: "/sp/outside/index.m3u8"},
{parent: "laosiji/movie/master.m3u8", child: "/shared/index.m3u8"},
} {
if _, err := ResolveChildSource(tt.parent, tt.child); err == nil {
t.Fatalf("unsafe child accepted: parent=%q child=%q", tt.parent, tt.child)
}
}
}