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:
parent
2534dbbe05
commit
6bc4d3d2f8
3 changed files with 24 additions and 18 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue