Skip to content

Commit

Permalink
Allow some content types to be inlined (#3274)
Browse files Browse the repository at this point in the history
"Shamelessly" stolen from
matrix-org/synapse#15988
  • Loading branch information
S7evinK authored Dec 12, 2023
1 parent fd11e65 commit 185ad6b
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
54 changes: 49 additions & 5 deletions mediaapi/routing/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,40 @@ type downloadRequest struct {
DownloadFilename string
}

// Taken from: https://github.com/matrix-org/synapse/blob/c3627d0f99ed5a23479305dc2bd0e71ca25ce2b1/synapse/media/_base.py#L53C1-L84
// A list of all content types that are "safe" to be rendered inline in a browser.
var allowInlineTypes = map[types.ContentType]struct{}{
"text/css": {},
"text/plain": {},
"text/csv": {},
"application/json": {},
"application/ld+json": {},
// We allow some media files deemed as safe, which comes from the matrix-react-sdk.
// https://github.com/matrix-org/matrix-react-sdk/blob/a70fcfd0bcf7f8c85986da18001ea11597989a7c/src/utils/blobs.ts#L51
// SVGs are *intentionally* omitted.
"image/jpeg": {},
"image/gif": {},
"image/png": {},
"image/apng": {},
"image/webp": {},
"image/avif": {},
"video/mp4": {},
"video/webm": {},
"video/ogg": {},
"video/quicktime": {},
"audio/mp4": {},
"audio/webm": {},
"audio/aac": {},
"audio/mpeg": {},
"audio/ogg": {},
"audio/wave": {},
"audio/wav": {},
"audio/x-wav": {},
"audio/x-pn-wav": {},
"audio/flac": {},
"audio/x-flac": {},
}

// Download implements GET /download and GET /thumbnail
// Files from this server (i.e. origin == cfg.ServerName) are served directly
// Files from remote servers (i.e. origin != cfg.ServerName) are cached locally.
Expand Down Expand Up @@ -353,7 +387,7 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
}

if len(filename) == 0 {
w.Header().Set("Content-Disposition", "attachment")
w.Header().Set("Content-Disposition", contentDispositionFor(""))
return nil
}

Expand Down Expand Up @@ -383,20 +417,21 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
unescaped = strings.ReplaceAll(unescaped, `\`, `\\"`)
unescaped = strings.ReplaceAll(unescaped, `"`, `\"`)

disposition := contentDispositionFor(responseMetadata.ContentType)
if isASCII {
// For ASCII filenames, we should only quote the filename if
// it needs to be done, e.g. it contains a space or a character
// that would otherwise be parsed as a control character in the
// Content-Disposition header
w.Header().Set("Content-Disposition", fmt.Sprintf(
`attachment; filename=%s%s%s`,
quote, unescaped, quote,
`%s; filename=%s%s%s`,
disposition, quote, unescaped, quote,
))
} else {
// For UTF-8 filenames, we quote always, as that's the standard
w.Header().Set("Content-Disposition", fmt.Sprintf(
`attachment; filename*=utf-8''%s`,
url.QueryEscape(unescaped),
`%s; filename*=utf-8''%s`,
disposition, url.QueryEscape(unescaped),
))
}

Expand Down Expand Up @@ -808,3 +843,12 @@ func (r *downloadRequest) fetchRemoteFile(

return types.Path(finalPath), duplicate, nil
}

// contentDispositionFor returns the Content-Disposition for a given
// content type.
func contentDispositionFor(contentType types.ContentType) string {
if _, ok := allowInlineTypes[contentType]; ok {
return "inline"
}
return "attachment"
}
13 changes: 13 additions & 0 deletions mediaapi/routing/download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package routing

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_dispositionFor(t *testing.T) {
assert.Equal(t, "attachment", contentDispositionFor(""), "empty content type")
assert.Equal(t, "attachment", contentDispositionFor("image/svg"), "image/svg")
assert.Equal(t, "inline", contentDispositionFor("image/jpeg"), "image/jpg")
}

0 comments on commit 185ad6b

Please sign in to comment.