Fix: Protokoll-relative URLs, PDF-Fragment-Merge, Startup-Token-Cache, Test-Nil-Deref

- URL-Normalisierung überspringt jetzt //protocol-relative URLs
- PDF-Viewer-Parameter werden mit bestehenden Fragments gemerged statt blind angehängt
- /api/startup-token setzt Cache-Control: no-store (Server + Client)
- Tote Goroutine mit ignoriertem net.Listen-Error aus TestAssetsServed entfernt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jesko Anschütz 2026-03-23 15:21:26 +01:00
parent 2534dbbe05
commit 6bc4d3d2f8
3 changed files with 24 additions and 18 deletions

View file

@ -349,8 +349,11 @@ func (a *App) fetchPlaylist(ctx context.Context) {
} }
for i := range pr.Items { for i := range pr.Items {
if strings.HasPrefix(pr.Items[i].Src, "/") { src := pr.Items[i].Src
pr.Items[i].Src = a.Config.ServerBaseURL + pr.Items[i].Src // Nur echte relative Pfade prefixen (einzelnes /), nicht protokoll-relative
// URLs (//cdn.example.com/...) und keine absoluten URLs (http://, https://).
if strings.HasPrefix(src, "/") && !strings.HasPrefix(src, "//") {
pr.Items[i].Src = a.Config.ServerBaseURL + src
} }
} }

View file

@ -108,6 +108,7 @@ func (s *Server) handleNowPlaying(w http.ResponseWriter, _ *http.Request) {
// wurde und lädt die Seite automatisch neu. // wurde und lädt die Seite automatisch neu.
func (s *Server) handleStartupToken(w http.ResponseWriter, _ *http.Request) { func (s *Server) handleStartupToken(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
json.NewEncoder(w).Encode(map[string]string{"token": s.startupToken}) //nolint:errcheck json.NewEncoder(w).Encode(map[string]string{"token": s.startupToken}) //nolint:errcheck
} }
@ -435,7 +436,23 @@ const playerHTML = `<!DOCTYPE html>
} else { } else {
// type === 'web', 'pdf' oder unbekannt → iframe // type === 'web', 'pdf' oder unbekannt → iframe
if (type === 'pdf') { if (type === 'pdf') {
frame.src = item.src + '#toolbar=0&navpanes=0&scrollbar=0&view=Fit&page=1'; frame.src = (function pdfUrl(src) {
var defaults = {toolbar: '0', navpanes: '0', scrollbar: '0', view: 'Fit', page: '1'};
var hashIdx = src.indexOf('#');
var base = hashIdx >= 0 ? src.substring(0, hashIdx) : src;
var existing = hashIdx >= 0 ? src.substring(hashIdx + 1) : '';
var params = {};
existing.split('&').forEach(function(p) {
var kv = p.split('=');
if (kv[0]) params[kv[0]] = kv[1] || '';
});
for (var k in defaults) {
if (!(k in params)) params[k] = defaults[k];
}
var parts = [];
for (var k in params) parts.push(k + '=' + params[k]);
return base + '#' + parts.join('&');
})(item.src);
} else { } else {
if (frame.src !== item.src) { frame.src = item.src; } if (frame.src !== item.src) { frame.src = item.src; }
} }
@ -603,7 +620,7 @@ const playerHTML = `<!DOCTYPE html>
var knownStartupToken = null; var knownStartupToken = null;
function pollStartupToken() { function pollStartupToken() {
fetch('/api/startup-token') fetch('/api/startup-token', {cache: 'no-store'})
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(d) { .then(function(d) {
if (!d || !d.token) return; if (!d || !d.token) return;

View file

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"io/fs" "io/fs"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@ -98,17 +97,6 @@ func TestHandleSysInfoReturnsItems(t *testing.T) {
} }
func TestAssetsServed(t *testing.T) { func TestAssetsServed(t *testing.T) {
s := New("127.0.0.1:0", func() NowPlaying { return NowPlaying{} })
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ready := make(chan string, 1)
go func() {
ln, _ := net.Listen("tcp", "127.0.0.1:0")
ready <- ln.Addr().String()
ln.Close()
}()
// Use httptest recorder to test asset handler directly via the embed FS. // Use httptest recorder to test asset handler directly via the embed FS.
sub, err := fs.Sub(assetsFS, "assets") sub, err := fs.Sub(assetsFS, "assets")
if err != nil { if err != nil {
@ -127,8 +115,6 @@ func TestAssetsServed(t *testing.T) {
t.Errorf("GET /assets/%s Content-Type = %q, want image/png", name, ct) t.Errorf("GET /assets/%s Content-Type = %q, want image/png", name, ct)
} }
} }
_ = s
_ = ctx
} }
func TestServerRunAndStop(t *testing.T) { func TestServerRunAndStop(t *testing.T) {