diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a0ea221..11c0e28 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -55,7 +55,7 @@ RUN tailwindcss \ # ----------------------------------------------------------------------------- # Stage 1: builder # ----------------------------------------------------------------------------- -FROM golang:1.24-alpine AS builder +FROM golang:1.25-alpine AS builder WORKDIR /build diff --git a/frontend/internal/handlers/base.go b/frontend/internal/handlers/base.go index 28e8ba6..6dc7d3c 100644 --- a/frontend/internal/handlers/base.go +++ b/frontend/internal/handlers/base.go @@ -190,8 +190,8 @@ var stubPageContent = map[string]string{ {{define "job-content"}}
}
@@ -57,12 +62,12 @@ func (h *PageHandler) TemplateDetailWithKey(w http.ResponseWriter, r *http.Reque
// Request a preview PNG from the backend's render endpoint.
// A 2-second sub-context timeout prevents a slow render from blocking the page.
- previewURI := "/static/preview-placeholder.svg"
+ previewURI := template.URL("/static/preview-placeholder.svg")
previewCtx, previewCancel := context.WithTimeout(r.Context(), 2*time.Second)
defer previewCancel()
previewBytes, previewErr := h.client.RenderPreview(previewCtx, key)
if previewErr == nil && len(previewBytes) > 0 {
- previewURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(previewBytes)
+ previewURI = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(previewBytes))
}
h.renderPage(w, r, "template", TemplateDetailData{
diff --git a/frontend/internal/handlers/template_test.go b/frontend/internal/handlers/template_test.go
index 7f77794..42df3d9 100644
--- a/frontend/internal/handlers/template_test.go
+++ b/frontend/internal/handlers/template_test.go
@@ -120,6 +120,33 @@ func TestTemplateDetailPreviewTimeout(t *testing.T) {
}
}
+// TestTemplateDetailPreviewDataURLNotEscaped is the regression test for
+// issue #87: html/template was escaping the `data:image/png;base64,...` URL
+// in src= attributes to `#ZgotmplZ` because PreviewURI was typed `string`
+// (default url-sanitisation kicks in). After the fix PreviewURI is
+// template.URL, marking it as already-safe so it round-trips through the
+// rendered HTML unmodified.
+func TestTemplateDetailPreviewDataURLNotEscaped(t *testing.T) {
+ t.Parallel()
+ backend := templateDetailBackend(t, true) // serve preview PNG
+ defer backend.Close()
+ ph := handlers.NewPageHandlerFromURL(t, backend.URL)
+ req := httptest.NewRequest(http.MethodGet, "/templates/"+templateKey, nil)
+ req.Header.Set("HX-Request", "true")
+ w := httptest.NewRecorder()
+ ph.TemplateDetailWithKey(w, req, templateKey)
+ if w.Code != http.StatusOK {
+ t.Fatalf("status %d, body: %s", w.Code, w.Body.String())
+ }
+ body := w.Body.String()
+ if !strings.Contains(body, "data:image/png;base64,") {
+ t.Errorf("preview src must contain data:image/png;base64 URL, got: %s", body)
+ }
+ if strings.Contains(body, "ZgotmplZ") {
+ t.Errorf("preview src is html-template-escaped (ZgotmplZ marker); PreviewURI must use template.URL type, got: %s", body)
+ }
+}
+
func TestTemplateDetailBackendError(t *testing.T) {
t.Parallel()
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {