@@ -63,6 +63,40 @@ type downloadRequest struct {
6363 DownloadFilename string
6464}
6565
66+ // Taken from: https://github.com/matrix-org/synapse/blob/c3627d0f99ed5a23479305dc2bd0e71ca25ce2b1/synapse/media/_base.py#L53C1-L84
67+ // A list of all content types that are "safe" to be rendered inline in a browser.
68+ var allowInlineTypes = map [types.ContentType ]struct {}{
69+ "text/css" : {},
70+ "text/plain" : {},
71+ "text/csv" : {},
72+ "application/json" : {},
73+ "application/ld+json" : {},
74+ // We allow some media files deemed as safe, which comes from the matrix-react-sdk.
75+ // https://github.com/matrix-org/matrix-react-sdk/blob/a70fcfd0bcf7f8c85986da18001ea11597989a7c/src/utils/blobs.ts#L51
76+ // SVGs are *intentionally* omitted.
77+ "image/jpeg" : {},
78+ "image/gif" : {},
79+ "image/png" : {},
80+ "image/apng" : {},
81+ "image/webp" : {},
82+ "image/avif" : {},
83+ "video/mp4" : {},
84+ "video/webm" : {},
85+ "video/ogg" : {},
86+ "video/quicktime" : {},
87+ "audio/mp4" : {},
88+ "audio/webm" : {},
89+ "audio/aac" : {},
90+ "audio/mpeg" : {},
91+ "audio/ogg" : {},
92+ "audio/wave" : {},
93+ "audio/wav" : {},
94+ "audio/x-wav" : {},
95+ "audio/x-pn-wav" : {},
96+ "audio/flac" : {},
97+ "audio/x-flac" : {},
98+ }
99+
66100// Download implements GET /download and GET /thumbnail
67101// Files from this server (i.e. origin == cfg.ServerName) are served directly
68102// Files from remote servers (i.e. origin != cfg.ServerName) are cached locally.
@@ -353,7 +387,7 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
353387 }
354388
355389 if len (filename ) == 0 {
356- w .Header ().Set ("Content-Disposition" , "attachment" )
390+ w .Header ().Set ("Content-Disposition" , contentDispositionFor ( "" ) )
357391 return nil
358392 }
359393
@@ -383,20 +417,21 @@ func (r *downloadRequest) addDownloadFilenameToHeaders(
383417 unescaped = strings .ReplaceAll (unescaped , `\` , `\\"` )
384418 unescaped = strings .ReplaceAll (unescaped , `"` , `\"` )
385419
420+ disposition := contentDispositionFor (responseMetadata .ContentType )
386421 if isASCII {
387422 // For ASCII filenames, we should only quote the filename if
388423 // it needs to be done, e.g. it contains a space or a character
389424 // that would otherwise be parsed as a control character in the
390425 // Content-Disposition header
391426 w .Header ().Set ("Content-Disposition" , fmt .Sprintf (
392- `attachment ; filename=%s%s%s` ,
393- quote , unescaped , quote ,
427+ `%s ; filename=%s%s%s` ,
428+ disposition , quote , unescaped , quote ,
394429 ))
395430 } else {
396431 // For UTF-8 filenames, we quote always, as that's the standard
397432 w .Header ().Set ("Content-Disposition" , fmt .Sprintf (
398- `attachment ; filename*=utf-8''%s` ,
399- url .QueryEscape (unescaped ),
433+ `%s ; filename*=utf-8''%s` ,
434+ disposition , url .QueryEscape (unescaped ),
400435 ))
401436 }
402437
@@ -808,3 +843,12 @@ func (r *downloadRequest) fetchRemoteFile(
808843
809844 return types .Path (finalPath ), duplicate , nil
810845}
846+
847+ // contentDispositionFor returns the Content-Disposition for a given
848+ // content type.
849+ func contentDispositionFor (contentType types.ContentType ) string {
850+ if _ , ok := allowInlineTypes [contentType ]; ok {
851+ return "inline"
852+ }
853+ return "attachment"
854+ }
0 commit comments