fix(files): never cache file downloads in v1 or v2
Move the Cache-Control: no-cache header into the shared WriteFileDownload so every export and attachment download carries it, and add it to the standalone v1 export download writer too. Downloads must never be cached.
This commit is contained in:
parent
b0c7bddb03
commit
91b447f020
|
|
@ -132,6 +132,10 @@ func DownloadUserDataExport(c *echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Downloads must never be cached; no-cache overrides the global no-store
|
||||||
|
// directive while still allowing revalidation.
|
||||||
|
c.Response().Header().Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
if config.FilesType.GetString() == "s3" {
|
if config.FilesType.GetString() == "s3" {
|
||||||
c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+exportFile.Name+"\"")
|
c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+exportFile.Name+"\"")
|
||||||
c.Response().Header().Set("Content-Type", exportFile.Mime)
|
c.Response().Header().Set("Content-Type", exportFile.Mime)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ import (
|
||||||
// (Range + If-Modified-Since for free), a manual 304 + io.Copy otherwise. It does
|
// (Range + If-Modified-Since for free), a manual 304 + io.Copy otherwise. It does
|
||||||
// not close the reader; the caller owns it.
|
// not close the reader; the caller owns it.
|
||||||
func WriteFileDownload(w http.ResponseWriter, r *http.Request, f *files.File) {
|
func WriteFileDownload(w http.ResponseWriter, r *http.Request, f *files.File) {
|
||||||
|
// Downloads must never be cached. no-cache overrides the global no-store
|
||||||
|
// directive so revalidation (If-Modified-Since) still works.
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
mimeToReturn := f.Mime
|
mimeToReturn := f.Mime
|
||||||
if mimeToReturn == "" {
|
if mimeToReturn == "" {
|
||||||
mimeToReturn = "application/octet-stream"
|
mimeToReturn = "application/octet-stream"
|
||||||
|
|
|
||||||
|
|
@ -62,18 +62,18 @@ func toAttachmentUploadError(err error) AttachmentUploadError {
|
||||||
|
|
||||||
// WriteAttachmentDownload streams the attachment (or its inline image preview) to
|
// WriteAttachmentDownload streams the attachment (or its inline image preview) to
|
||||||
// the response and closes the file reader. The non-preview path delegates to
|
// the response and closes the file reader. The non-preview path delegates to
|
||||||
// WriteFileDownload, adding the cache override that lets browsers cache attachments.
|
// WriteFileDownload, which sets Cache-Control: no-cache; the preview branch returns
|
||||||
|
// early, so it sets the same header itself.
|
||||||
func WriteAttachmentDownload(w http.ResponseWriter, r *http.Request, ta *models.TaskAttachment, preview []byte) {
|
func WriteAttachmentDownload(w http.ResponseWriter, r *http.Request, ta *models.TaskAttachment, preview []byte) {
|
||||||
defer func() { _ = ta.File.File.Close() }()
|
defer func() { _ = ta.File.File.Close() }()
|
||||||
|
|
||||||
if preview != nil {
|
if preview != nil {
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Content-Type", "image/png")
|
w.Header().Set("Content-Type", "image/png")
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(preview)))
|
w.Header().Set("Content-Length", strconv.Itoa(len(preview)))
|
||||||
_, _ = w.Write(preview)
|
_, _ = w.Write(preview)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the global no-store directive so browsers can cache attachments.
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
WriteFileDownload(w, r, ta.File)
|
WriteFileDownload(w, r, ta.File)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ func TestHumaUserExport(t *testing.T) {
|
||||||
require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
|
require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
|
||||||
assert.Equal(t, content, rec.Body.Bytes(), "the streamed export bytes must match")
|
assert.Equal(t, content, rec.Body.Bytes(), "the streamed export bytes must match")
|
||||||
assert.Contains(t, rec.Header().Get("Content-Disposition"), "test")
|
assert.Contains(t, rec.Header().Get("Content-Disposition"), "test")
|
||||||
|
assert.Equal(t, "no-cache", rec.Header().Get("Cache-Control"), "downloads must never be cached")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Download with a wrong password is refused", func(t *testing.T) {
|
t.Run("Download with a wrong password is refused", func(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue