fix: harden playback and security edge cases

This commit is contained in:
ShukeBta
2026-06-25 02:33:30 +08:00
parent 02fe4fd374
commit 9021007027
5 changed files with 113 additions and 8 deletions
-5
View File
@@ -109,9 +109,6 @@ func proxyCloudResolvedLink(playback cloudPlaybackRequest) {
clientMethod = http.MethodGet
}
upstreamMethod := clientMethod
if upstreamMethod == http.MethodHead {
upstreamMethod = http.MethodGet
}
req, err := http.NewRequestWithContext(c.Request.Context(), upstreamMethod, playback.link.URL, nil)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
@@ -122,8 +119,6 @@ func proxyCloudResolvedLink(playback cloudPlaybackRequest) {
}
if rng := c.GetHeader("Range"); rng != "" {
req.Header.Set("Range", rng)
} else if clientMethod == http.MethodHead {
req.Header.Set("Range", "bytes=0-0")
}
if accept := c.GetHeader("Accept"); accept != "" {
req.Header.Set("Accept", accept)
+55
View File
@@ -0,0 +1,55 @@
package handler
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/ShukeBta/MediaStationGo/internal/service/cloud"
)
func TestProxyCloudResolvedLinkUsesHEADWithoutSyntheticRange(t *testing.T) {
gin.SetMode(gin.TestMode)
var upstreamMethod, upstreamRange string
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
upstreamMethod = r.Method
upstreamRange = r.Header.Get("Range")
w.Header().Set("Content-Type", "video/mp4")
w.Header().Set("Content-Length", "123456")
w.Header().Set("Accept-Ranges", "bytes")
w.WriteHeader(http.StatusOK)
}))
defer upstream.Close()
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodHead, "/api/cloud/play/openlist?ref=movie", nil)
proxyCloudResolvedLink(cloudPlaybackRequest{
c: c,
typ: "openlist",
ref: "movie",
link: &cloud.DirectLink{
URL: upstream.URL + "/movie.mp4",
Proxy: true,
},
})
if upstreamMethod != http.MethodHead {
t.Fatalf("upstream method = %q, want HEAD", upstreamMethod)
}
if upstreamRange != "" {
t.Fatalf("upstream Range = %q, want empty", upstreamRange)
}
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
if got := rec.Header().Get("Content-Length"); got != "123456" {
t.Fatalf("Content-Length = %q, want full upstream length", got)
}
if rec.Body.Len() != 0 {
t.Fatalf("HEAD response body length = %d, want 0", rec.Body.Len())
}
}
+12 -2
View File
@@ -38,9 +38,9 @@ func EvaluateFFmpegSecurity(versionLine string) FFmpegSecurityStatus {
FixedVersion: ffmpegPixelSmashFixedVersion,
ParsedVersion: fmt.Sprintf("%d.%d.%d", major, minor, patch),
}
if major == 8 && minor == 1 && patch < 2 {
if major == 8 && ffmpegVersionLess(major, minor, patch, 8, 1, 2) {
status.Status = "vulnerable"
status.Message = "当前 FFmpeg 8.1.x 版本低于 8.1.2,可能受 PixelSmash 解码漏洞影响;请升级 ffmpeg/ffprobe"
status.Message = "当前 FFmpeg 8.x 版本低于 8.1.2,可能受 PixelSmash 解码漏洞影响;请升级 ffmpeg/ffprobe"
return status
}
if major < 8 {
@@ -50,6 +50,16 @@ func EvaluateFFmpegSecurity(versionLine string) FFmpegSecurityStatus {
return status
}
func ffmpegVersionLess(major, minor, patch, fixedMajor, fixedMinor, fixedPatch int) bool {
if major != fixedMajor {
return major < fixedMajor
}
if minor != fixedMinor {
return minor < fixedMinor
}
return patch < fixedPatch
}
func parseFFmpegVersionLine(versionLine string) (major, minor, patch int, ok bool) {
match := ffmpegVersionRE.FindStringSubmatch(strings.TrimSpace(versionLine))
if len(match) < 3 {
+45
View File
@@ -0,0 +1,45 @@
package service
import "testing"
func TestEvaluateFFmpegSecurityClassifiesPixelSmashFixedVersion(t *testing.T) {
tests := []struct {
name string
versionLine string
want string
}{
{
name: "ffmpeg 8.0 is vulnerable",
versionLine: "ffmpeg version 8.0 Copyright",
want: "vulnerable",
},
{
name: "ffmpeg 8.1.1 is vulnerable",
versionLine: "ffmpeg version 8.1.1 Copyright",
want: "vulnerable",
},
{
name: "ffmpeg 8.1.2 is ok",
versionLine: "ffmpeg version 8.1.2 Copyright",
want: "ok",
},
{
name: "older major needs distro review",
versionLine: "ffmpeg version 7.1.2 Copyright",
want: "review",
},
{
name: "unrecognized is unknown",
versionLine: "not ffmpeg",
want: "unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := EvaluateFFmpegSecurity(tt.versionLine); got.Status != tt.want {
t.Fatalf("status = %q, want %q (%+v)", got.Status, tt.want, got)
}
})
}
}
+1 -1
View File
@@ -106,7 +106,7 @@ function artworkIdentity(url) {
return `${url.origin}${url.pathname}?url=${url.searchParams.get('url') || ''}`
}
if (url.pathname.startsWith('/api/cloud/play/')) {
return `${url.origin}${url.pathname}`
return `${url.origin}${url.pathname}?ref=${url.searchParams.get('ref') || ''}`
}
return ''
}