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
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:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user