fix(export): explicit Content-Length on the download responses
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 1m2s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m54s

Both hops (backend attachment + gateway /dl forward) wrote the body
through net/http's buffered writer, so anything over the write buffer
went out Transfer-Encoding: chunked. Android's system DownloadManager —
the executor behind VKWebAppDownloadFile — hangs indefinitely on a
download of unknown length (observed on-device: VK Android stuck in
'downloading'); Telegram's downloader tolerates chunked, which is why
only VK broke. The artifact is fully buffered anyway, so the length is
known — set it on both responses.
This commit is contained in:
Ilia Denisov
2026-07-02 19:02:04 +02:00
parent 3471d40576
commit c3c27bba5e
2 changed files with 9 additions and 1 deletions
+5 -1
View File
@@ -176,11 +176,15 @@ func subtleNeq(got, want string) bool {
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) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("Content-Disposition", `attachment; filename="`+sanitizeFilename(filename)+`"`)
c.Header("Cache-Control", "private, max-age=0")
c.Header("Content-Length", strconv.Itoa(len(data)))
c.Data(http.StatusOK, contentType, data)
}
+4
View File
@@ -14,6 +14,7 @@ import (
"io"
"net"
"net/http"
"strconv"
"strings"
"time"
@@ -465,6 +466,9 @@ func (s *Server) exportDownloadHandler() http.Handler {
}
w.Header().Set("X-Content-Type-Options", "nosniff")
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)
})
}