mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
harden manifest detection and downloader failure coverage
This commit is contained in:
@@ -386,7 +386,7 @@ func isManifestResponse(contentType string, peek []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
s := strings.TrimSpace(strings.ToLower(string(peek)))
|
s := strings.TrimSpace(strings.ToLower(string(peek)))
|
||||||
if strings.HasPrefix(s, "<?xml") && strings.Contains(s, "<mpd") {
|
if (strings.HasPrefix(s, "<?xml") && strings.Contains(s, "<mpd")) || strings.HasPrefix(s, "<mpd") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(s, "#extm3u") {
|
if strings.HasPrefix(s, "#extm3u") {
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/blowfish"
|
"golang.org/x/crypto/blowfish"
|
||||||
)
|
)
|
||||||
@@ -46,9 +49,15 @@ func TestManifestDetection(t *testing.T) {
|
|||||||
if !isManifestResponse("application/dash+xml", []byte("x")) {
|
if !isManifestResponse("application/dash+xml", []byte("x")) {
|
||||||
t.Fatalf("expected dash content-type to be manifest")
|
t.Fatalf("expected dash content-type to be manifest")
|
||||||
}
|
}
|
||||||
|
if !isManifestResponse("application/vnd.apple.mpegurl", []byte("x")) {
|
||||||
|
t.Fatalf("expected mpegurl content-type to be manifest")
|
||||||
|
}
|
||||||
if !isManifestResponse("application/octet-stream", []byte("<?xml version='1.0'?><MPD></MPD>")) {
|
if !isManifestResponse("application/octet-stream", []byte("<?xml version='1.0'?><MPD></MPD>")) {
|
||||||
t.Fatalf("expected MPD XML body to be manifest")
|
t.Fatalf("expected MPD XML body to be manifest")
|
||||||
}
|
}
|
||||||
|
if !isManifestResponse("application/octet-stream", []byte("<MPD type='static'></MPD>")) {
|
||||||
|
t.Fatalf("expected MPD body without xml prolog to be manifest")
|
||||||
|
}
|
||||||
if !isManifestResponse("text/plain", []byte("#EXTM3U\n#EXT-X-VERSION:3")) {
|
if !isManifestResponse("text/plain", []byte("#EXTM3U\n#EXT-X-VERSION:3")) {
|
||||||
t.Fatalf("expected HLS body to be manifest")
|
t.Fatalf("expected HLS body to be manifest")
|
||||||
}
|
}
|
||||||
@@ -158,3 +167,67 @@ func TestFileDeezerEncryptedTruncatedResponseRemovesPartialFile(t *testing.T) {
|
|||||||
t.Fatalf("expected no partial file, stat err=%v", statErr)
|
t.Fatalf("expected no partial file, stat err=%v", statErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileDeezerEncryptedBadStatus(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
d := NewWithOptions(true, false)
|
||||||
|
out := filepath.Join(t.TempDir(), "x", "a.flac")
|
||||||
|
err := d.FileDeezerEncrypted(context.Background(), ts.URL, out, "3135556")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "status=403") {
|
||||||
|
t.Fatalf("expected status error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloaderFileContextCancellationRemovesPartialFile(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
_, _ = w.Write([]byte("partial-data"))
|
||||||
|
if f, ok := w.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
<-r.Context().Done()
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
d := NewWithOptions(true, false)
|
||||||
|
out := filepath.Join(t.TempDir(), "x", "cancel.bin")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
err := d.File(ctx, ts.URL, out)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected context cancellation error")
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(out); !errors.Is(statErr, os.ErrNotExist) {
|
||||||
|
t.Fatalf("expected no partial file, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreamManifestWithFFmpegMissing(t *testing.T) {
|
||||||
|
d := NewWithOptions(true, false)
|
||||||
|
t.Setenv("PATH", "")
|
||||||
|
err := d.streamManifestWithFFmpeg(context.Background(), "https://example.com/live.m3u8", filepath.Join(t.TempDir(), "out.m4a"), false)
|
||||||
|
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "ffmpeg not found") {
|
||||||
|
t.Fatalf("expected ffmpeg missing error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreamManifestWithFFmpegFailureRemovesPartialFile(t *testing.T) {
|
||||||
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
|
t.Skip("ffmpeg not installed")
|
||||||
|
}
|
||||||
|
d := NewWithOptions(true, false)
|
||||||
|
out := filepath.Join(t.TempDir(), "out.m4a")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err := d.streamManifestWithFFmpeg(ctx, "https://127.0.0.1:1/unreachable.m3u8", out, false)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "ffmpeg stream copy failed") {
|
||||||
|
t.Fatalf("expected ffmpeg failure error, got %v", err)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(out); !errors.Is(statErr, os.ErrNotExist) {
|
||||||
|
t.Fatalf("expected no partial file after ffmpeg failure, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user