feat(export): server-rendered artifacts behind one signed download URL #160

Merged
developer merged 7 commits from feature/export-server-render into development 2026-07-02 21:58:07 +00:00
2 changed files with 9 additions and 1 deletions
Showing only changes of commit c3c27bba5e - Show all commits
+5 -1
View File
@@ -176,11 +176,15 @@ func subtleNeq(got, want string) bool {
return !hmac.Equal([]byte(got), []byte(want)) return !hmac.Equal([]byte(got), []byte(want))
} }
// serveAttachment writes bytes as a named file download. // serveAttachment writes bytes as a named file download. Content-Length is set
// explicitly: a body over net/http's write buffer otherwise goes out chunked, and
// Android's system DownloadManager (which VKWebAppDownloadFile delegates to) hangs
// forever on a download of unknown length.
func serveAttachment(c *gin.Context, filename, contentType string, data []byte) { func serveAttachment(c *gin.Context, filename, contentType string, data []byte) {
c.Header("X-Content-Type-Options", "nosniff") c.Header("X-Content-Type-Options", "nosniff")
c.Header("Content-Disposition", `attachment; filename="`+sanitizeFilename(filename)+`"`) c.Header("Content-Disposition", `attachment; filename="`+sanitizeFilename(filename)+`"`)
c.Header("Cache-Control", "private, max-age=0") c.Header("Cache-Control", "private, max-age=0")
c.Header("Content-Length", strconv.Itoa(len(data)))
c.Data(http.StatusOK, contentType, data) c.Data(http.StatusOK, contentType, data)
} }
+4
View File
@@ -14,6 +14,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@@ -465,6 +466,9 @@ func (s *Server) exportDownloadHandler() http.Handler {
} }
w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Cache-Control", "private, max-age=0") w.Header().Set("Cache-Control", "private, max-age=0")
// Explicit length, or the body goes out chunked and Android's system
// DownloadManager (the VKWebAppDownloadFile executor) hangs on it.
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
_, _ = w.Write(data) _, _ = w.Write(data)
}) })
} }