enforce pure cloud 302 playback

This commit is contained in:
ShukeBta
2026-06-13 17:45:10 +08:00
parent b6047af9fb
commit b2b43e58da
2 changed files with 312 additions and 41 deletions
+151 -33
View File
@@ -313,19 +313,15 @@ func Test115QRFlow(t *testing.T) {
}
func TestCloudDrive2WebDAVListAndResolve(t *testing.T) {
var gotAuth, gotDepth string
var gotAuth, gotDepth, gotRange string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
gotDepth = r.Header.Get("Depth")
if r.Method != "PROPFIND" {
t.Fatalf("unexpected method %s", r.Method)
}
if r.URL.Path != "/dav" {
t.Fatalf("unexpected path %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusMultiStatus)
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
switch {
case r.Method == "PROPFIND" && r.URL.Path == "/dav":
gotAuth = r.Header.Get("Authorization")
gotDepth = r.Header.Get("Depth")
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusMultiStatus)
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/</d:href>
@@ -340,6 +336,13 @@ func TestCloudDrive2WebDAVListAndResolve(t *testing.T) {
<d:propstat><d:prop><d:displayname>Movie.mkv</d:displayname><d:getcontentlength>789</d:getcontentlength><d:resourcetype/></d:prop></d:propstat>
</d:response>
</d:multistatus>`))
case r.Method == http.MethodGet && r.URL.Path == "/dav/123/Movie.mkv":
gotAuth = r.Header.Get("Authorization")
gotRange = r.Header.Get("Range")
http.Redirect(w, r, "https://cdn.example.test/123/Movie.mkv?sign=1", http.StatusFound)
default:
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
}
}))
defer srv.Close()
@@ -370,14 +373,46 @@ func TestCloudDrive2WebDAVListAndResolve(t *testing.T) {
if err != nil {
t.Fatalf("resolve: %v", err)
}
if link.URL != srv.URL+"/dav/123/Movie.mkv" {
if link.URL != "https://cdn.example.test/123/Movie.mkv?sign=1" {
t.Fatalf("bad url: %s", link.URL)
}
if !link.Proxy {
t.Fatalf("clouddrive2 should default to proxy mode")
if link.Proxy || len(link.Headers) != 0 {
t.Fatalf("clouddrive2 video should resolve to pure 302 link: %#v", link)
}
if !strings.HasPrefix(link.Headers["Authorization"], "Basic ") {
t.Fatalf("resolve must carry basic auth: %#v", link.Headers)
if gotRange != "bytes=0-0" {
t.Fatalf("resolve should probe with a tiny range, got %q", gotRange)
}
}
func TestCloudDrive2ResolveRejectsWebDAVProxyFallbackWithoutRedirect(t *testing.T) {
var getSeen bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "PROPFIND" && r.URL.Path == "/dav":
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusMultiStatus)
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?><d:multistatus xmlns:d="DAV:"><d:response><d:href>/dav/</d:href><d:propstat><d:prop><d:resourcetype><d:collection/></d:resourcetype></d:prop></d:propstat></d:response></d:multistatus>`))
case r.Method == http.MethodGet && r.URL.Path == "/dav/123/Movie.mkv":
getSeen = true
w.Header().Set("Content-Range", "bytes 0-0/10")
w.WriteHeader(http.StatusPartialContent)
_, _ = w.Write([]byte("x"))
default:
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
}
}))
defer srv.Close()
p, err := New(TypeCloudDrive2, map[string]any{"url": srv.URL + "/dav", "username": "u", "password": "p"}, srv.Client())
if err != nil {
t.Fatal(err)
}
_, err = p.Resolve(context.Background(), "/123/Movie.mkv")
if err == nil || !strings.Contains(err.Error(), "without CDN Location") || !strings.Contains(err.Error(), "refusing WebDAV/proxy fallback") {
t.Fatalf("resolve error = %v, want pure 302 refusal", err)
}
if !getSeen {
t.Fatal("expected CloudDrive2 WebDAV direct-link probe")
}
}
@@ -433,15 +468,9 @@ func TestOpenListWebDAVListAndResolve(t *testing.T) {
if len(entries) != 1 || entries[0].ID != "/Cloud/Movie.mkv" || entries[0].Size != 1024 {
t.Fatalf("entries = %#v", entries)
}
link, err := p.Resolve(context.Background(), entries[0].ID)
if err != nil {
t.Fatalf("resolve: %v", err)
}
if link.URL != srv.URL+"/dav/Cloud/Movie.mkv" {
t.Fatalf("bad url: %s", link.URL)
}
if !link.Proxy {
t.Fatalf("openlist should default to proxy mode")
_, err = p.Resolve(context.Background(), entries[0].ID)
if err == nil || !strings.Contains(err.Error(), "pure 302 playback requires OpenList raw_url") {
t.Fatalf("openlist video resolve should require raw_url instead of WebDAV proxy fallback, err=%v", err)
}
}
@@ -480,6 +509,41 @@ func TestOpenListResolveUsesAPIRawURLFor302Playback(t *testing.T) {
}
}
func TestOpenListResolveCollapsesHostedRawURLRedirectToCDN(t *testing.T) {
var probeSeen bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/fs/get":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code":200,"data":{"raw_url":"/d/Cloud/Movie.mkv?sign=1"}}`))
case "/d/Cloud/Movie.mkv":
probeSeen = true
if r.Header.Get("Range") != "bytes=0-0" {
t.Fatalf("probe Range = %q", r.Header.Get("Range"))
}
http.Redirect(w, r, "https://cdn.example.test/movie.mkv?sign=cdn", http.StatusFound)
default:
t.Fatalf("unexpected path %s", r.URL.Path)
}
}))
defer srv.Close()
p, err := New(TypeOpenList, map[string]any{"server": srv.URL, "token": "alist-token"}, srv.Client())
if err != nil {
t.Fatal(err)
}
link, err := p.Resolve(context.Background(), "/Cloud/Movie.mkv")
if err != nil {
t.Fatalf("resolve: %v", err)
}
if !probeSeen {
t.Fatal("expected OpenList-hosted raw_url probe")
}
if link.URL != "https://cdn.example.test/movie.mkv?sign=cdn" || link.Proxy || len(link.Headers) != 0 {
t.Fatalf("link = %#v, want collapsed CDN 302 playback", link)
}
}
func TestOpenListResolveLogsInWithUsernamePasswordForAPIRawURL(t *testing.T) {
var loginSeen bool
var gotAuth string
@@ -524,7 +588,7 @@ func TestOpenListResolveLogsInWithUsernamePasswordForAPIRawURL(t *testing.T) {
}
}
func TestOpenListResolveFallsBackToProxyWhenAPIRawURLNeedsHeaders(t *testing.T) {
func TestOpenListResolveRejectsProxyWhenAPIRawURLNeedsHeaders(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/fs/get" {
t.Fatalf("unexpected path %s", r.URL.Path)
@@ -538,15 +602,69 @@ func TestOpenListResolveFallsBackToProxyWhenAPIRawURLNeedsHeaders(t *testing.T)
if err != nil {
t.Fatal(err)
}
link, err := p.Resolve(context.Background(), "/Cloud/Movie.mkv")
_, err = p.Resolve(context.Background(), "/Cloud/Movie.mkv")
if err == nil || !strings.Contains(err.Error(), "refusing WebDAV/proxy fallback") || !strings.Contains(err.Error(), "Cookie") {
t.Fatalf("resolve error = %v, want pure 302 refusal with header names", err)
}
}
func TestOpenListResolveRejectsHostedRawURLWithoutCDNRedirect(t *testing.T) {
var probeSeen bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/fs/get":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code":200,"data":{"raw_url":"/d/Cloud/Movie.mkv?sign=1"}}`))
case "/d/Cloud/Movie.mkv":
probeSeen = true
w.Header().Set("Content-Range", "bytes 0-0/10")
w.WriteHeader(http.StatusPartialContent)
_, _ = w.Write([]byte("x"))
default:
t.Fatalf("unexpected path %s", r.URL.Path)
}
}))
defer srv.Close()
p, err := New(TypeOpenList, map[string]any{"server": srv.URL, "token": "alist-token"}, srv.Client())
if err != nil {
t.Fatalf("resolve: %v", err)
t.Fatal(err)
}
if link.URL != srv.URL+"/dav/Cloud/Movie.mkv" {
t.Fatalf("url = %q", link.URL)
_, err = p.Resolve(context.Background(), "/Cloud/Movie.mkv")
if err == nil || !strings.Contains(err.Error(), "OpenList-hosted raw_url") || !strings.Contains(err.Error(), "no CDN Location") {
t.Fatalf("resolve error = %v, want hosted raw_url refusal", err)
}
if !link.Proxy || link.Headers["Cookie"] != "sid=abc" {
t.Fatalf("link should keep proxy mode with required headers: %#v", link)
if !probeSeen {
t.Fatal("expected OpenList-hosted raw_url probe")
}
}
func TestOpenListResolveDoesNotFallbackToWebDAVWhenAPIRawURLFails(t *testing.T) {
var davSeen bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/fs/get":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code":500,"message":"driver cannot provide raw_url"}`))
case "/dav/Cloud/Movie.mkv":
davSeen = true
w.WriteHeader(http.StatusOK)
default:
t.Fatalf("unexpected path %s", r.URL.Path)
}
}))
defer srv.Close()
p, err := New(TypeOpenList, map[string]any{"server": srv.URL, "token": "alist-token"}, srv.Client())
if err != nil {
t.Fatal(err)
}
_, err = p.Resolve(context.Background(), "/Cloud/Movie.mkv")
if err == nil || !strings.Contains(err.Error(), "pure 302 playback requires OpenList raw_url") {
t.Fatalf("resolve error = %v, want raw_url requirement", err)
}
if davSeen {
t.Fatal("openlist video resolve fell back to WebDAV after raw_url failure")
}
}