From bd984fc238db643245f86accc2f03389289bcb58 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 11:55:48 +0200 Subject: [PATCH 01/15] Rework raw file http header logic - Always respect the user's configured mime type map - Allow more types like image/pdf/video/audio to serve with correct content-type - Shorten cache duration of raw files to 5 minutes, matching GitHub - Don't set content-disposition: attachment, let the browser decide - Implement rfc5987 for filenames, remove previous hack --- modules/typesniffer/typesniffer.go | 11 +++++ routers/common/repo.go | 74 +++++++++++++++--------------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index b6a6646d50ce9..a76de53e3896e 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -70,6 +70,17 @@ func (ct SniffedType) IsRepresentableAsText() bool { return ct.IsText() || ct.IsSvgImage() } +// IsBrowsableType returns whether the file content can be displayed in a browser +// note, this excludes text types +func (ct SniffedType) IsBrowsableType() bool { + return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() +} + +// GetContentType returns the contentType +func (ct SniffedType) GetContentType() string { + return ct.contentType +} + // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. func DetectContentType(data []byte) SniffedType { if len(data) == 0 { diff --git a/routers/common/repo.go b/routers/common/repo.go index b3cd749115fb1..26191e9d06609 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -7,6 +7,7 @@ package common import ( "fmt" "io" + "net/url" "path" "path/filepath" "strings" @@ -42,7 +43,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err } // ServeData download file from io.Reader -func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { +func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error { buf := make([]byte, 1024) n, err := util.ReadAtMost(reader, buf) if err != nil { @@ -52,55 +53,56 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) buf = buf[:n] } - ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") + httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute) if size >= 0 { ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } else { - log.Error("ServeData called to serve data: %s with size < 0: %d", name, size) + log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size) } - name = path.Base(name) - - // Google Chrome dislike commas in filenames, so let's change it to a space - name = strings.ReplaceAll(name, ",", " ") + fileName := path.Base(filePath) st := typesniffer.DetectContentType(buf) + isBrowsableType := st.IsBrowsableType() + fileExtension := strings.ToLower(filepath.Ext(fileName)) + mimeType := "" + cs := "" - mappedMimeType := "" if setting.MimeTypeMap.Enabled { - fileExtension := strings.ToLower(filepath.Ext(name)) - mappedMimeType = setting.MimeTypeMap.Map[fileExtension] + mimeType = setting.MimeTypeMap.Map[fileExtension] } - if st.IsText() || ctx.FormBool("render") { - cs, err := charset.DetectEncoding(buf) - if err != nil { - log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err) - cs = "utf-8" - } - if mappedMimeType == "" { - mappedMimeType = "text/plain" - } - ctx.Resp.Header().Set("Content-Type", mappedMimeType+"; charset="+strings.ToLower(cs)) - } else { - ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") - if mappedMimeType != "" { - ctx.Resp.Header().Set("Content-Type", mappedMimeType) - } - if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) { - ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name)) - if st.IsSvgImage() || st.IsPDF() { - ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") - ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") - if st.IsSvgImage() { - ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType) - } else { - ctx.Resp.Header().Set("Content-Type", typesniffer.ApplicationOctetStream) + + if mimeType == "" { + if isBrowsableType { + mimeType = st.GetContentType() + } else { + if st.IsText() || ctx.FormBool("render") { + mimeType = "text/plain" + + cs, err = charset.DetectEncoding(buf) + if err != nil { + log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) + cs = "utf-8" } + } else { + mimeType = typesniffer.ApplicationOctetStream } - } else { - ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) } } + if cs != "" { + ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(cs)) + } else { + ctx.Resp.Header().Set("Content-Type", mimeType) + } + ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") + + // serve types that can present a security risk with CSP + if st.IsImage() || st.IsPDF() { + ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") + } + + ctx.Resp.Header().Set("Content-Disposition", `inline; filename*=UTF-8''`+url.PathEscape(fileName)) + ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") _, err = ctx.Resp.Write(buf) if err != nil { From 79876c709331265aa0bce860437c6cab163350b3 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 21:21:14 +0200 Subject: [PATCH 02/15] fix charset logic --- routers/common/repo.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 26191e9d06609..7095e648c3dba 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -42,6 +42,15 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) } +func getCharset(buf []byte, filePath string) string { + cs, err := charset.DetectEncoding(buf) + if err != nil { + log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) + return "utf-8" + } + return cs +} + // ServeData download file from io.Reader func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error { buf := make([]byte, 1024) @@ -75,20 +84,17 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read if mimeType == "" { if isBrowsableType { mimeType = st.GetContentType() + } else if st.IsText() || ctx.FormBool("render") { + mimeType = "text/plain" } else { - if st.IsText() || ctx.FormBool("render") { - mimeType = "text/plain" - - cs, err = charset.DetectEncoding(buf) - if err != nil { - log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) - cs = "utf-8" - } - } else { - mimeType = typesniffer.ApplicationOctetStream - } + mimeType = typesniffer.ApplicationOctetStream } } + + if st.IsText() || ctx.FormBool("render") { + cs = getCharset(buf, filePath) + } + if cs != "" { ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(cs)) } else { From 0ca4edcb537d3a1666406ff8e17c3d603af16789 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 21:22:36 +0200 Subject: [PATCH 03/15] simplify --- routers/common/repo.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 7095e648c3dba..ef1d59995d555 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -42,15 +42,6 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) } -func getCharset(buf []byte, filePath string) string { - cs, err := charset.DetectEncoding(buf) - if err != nil { - log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) - return "utf-8" - } - return cs -} - // ServeData download file from io.Reader func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error { buf := make([]byte, 1024) @@ -92,7 +83,11 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } if st.IsText() || ctx.FormBool("render") { - cs = getCharset(buf, filePath) + cs, err = charset.DetectEncoding(buf) + if err != nil { + log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) + cs = "utf-8" + } } if cs != "" { From 729fdbb19afb084297fb9e536bcb1c3fb266648b Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 22:59:56 +0200 Subject: [PATCH 04/15] small optimization --- routers/common/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index ef1d59995d555..286de3bfb2c6e 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -64,11 +64,11 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read fileName := path.Base(filePath) st := typesniffer.DetectContentType(buf) isBrowsableType := st.IsBrowsableType() - fileExtension := strings.ToLower(filepath.Ext(fileName)) mimeType := "" cs := "" if setting.MimeTypeMap.Enabled { + fileExtension := strings.ToLower(filepath.Ext(fileName)) mimeType = setting.MimeTypeMap.Map[fileExtension] } From a6b1400d129be30453856a8043a7a402a4a3c607 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 23:00:55 +0200 Subject: [PATCH 05/15] another optimization --- routers/common/repo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 286de3bfb2c6e..17660cec52fbf 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -63,7 +63,6 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read fileName := path.Base(filePath) st := typesniffer.DetectContentType(buf) - isBrowsableType := st.IsBrowsableType() mimeType := "" cs := "" @@ -73,7 +72,7 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } if mimeType == "" { - if isBrowsableType { + if st.IsBrowsableType() { mimeType = st.GetContentType() } else if st.IsText() || ctx.FormBool("render") { mimeType = "text/plain" From ec4ce64f2efb60aa4c270fe1e6c0c6f542c4295f Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 23 Jul 2022 23:48:25 +0200 Subject: [PATCH 06/15] one more optimization --- routers/common/repo.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 17660cec52fbf..9bcf929acd6dd 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -63,6 +63,7 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read fileName := path.Base(filePath) st := typesniffer.DetectContentType(buf) + isPlain := st.IsText() || ctx.FormBool("render") mimeType := "" cs := "" @@ -74,14 +75,14 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read if mimeType == "" { if st.IsBrowsableType() { mimeType = st.GetContentType() - } else if st.IsText() || ctx.FormBool("render") { + } else if isPlain { mimeType = "text/plain" } else { mimeType = typesniffer.ApplicationOctetStream } } - if st.IsText() || ctx.FormBool("render") { + if isPlain { cs, err = charset.DetectEncoding(buf) if err != nil { log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) From 0d9d34294abd2aec211765c332685319f94927d8 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 24 Jul 2022 10:38:47 +0200 Subject: [PATCH 07/15] Remove sandbox attribute from PDF to make it work in Safari --- routers/common/repo.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 9bcf929acd6dd..10cb9c3947182 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -98,10 +98,16 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") // serve types that can present a security risk with CSP - if st.IsImage() || st.IsPDF() { + if st.IsSvgImage() { ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") + } else if st.IsPDF() { + // no sandbox attribute for pdf as it breaks rendering in at least safari. this + // should generally be safe as scripts inside PDF can not escape the PDF document + // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion + ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") } + ctx.Resp.Header().Set("Content-Disposition", `inline; filename*=UTF-8''`+url.PathEscape(fileName)) ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") From 5fc9c00f36eeea677d73f36ed8949a945770c379 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 24 Jul 2022 10:42:40 +0200 Subject: [PATCH 08/15] fix lint --- routers/common/repo.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 10cb9c3947182..9b55789b3084f 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -107,7 +107,6 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") } - ctx.Resp.Header().Set("Content-Disposition", `inline; filename*=UTF-8''`+url.PathEscape(fileName)) ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") From e1d01582b04c06660c13aa7e1c245481fa23857c Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 25 Jul 2022 19:55:18 +0200 Subject: [PATCH 09/15] refactor typesniffer functions --- modules/typesniffer/typesniffer.go | 11 +++++------ routers/common/repo.go | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index a76de53e3896e..48c4a1da70b96 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -70,15 +70,14 @@ func (ct SniffedType) IsRepresentableAsText() bool { return ct.IsText() || ct.IsSvgImage() } -// IsBrowsableType returns whether the file content can be displayed in a browser -// note, this excludes text types -func (ct SniffedType) IsBrowsableType() bool { +// IsBrowsableType returns whether a non-text type can be displayed in a browser +func (ct SniffedType) IsBrowsableBinaryType() bool { return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() } -// GetContentType returns the contentType -func (ct SniffedType) GetContentType() string { - return ct.contentType +// GetMimeType returns the mime type +func (ct SniffedType) GetMimeType() string { + return ct.contentType[:strings.Index(ct.contentType, ";")] } // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. diff --git a/routers/common/repo.go b/routers/common/repo.go index 9b55789b3084f..2cc3754bedb07 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -73,8 +73,8 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } if mimeType == "" { - if st.IsBrowsableType() { - mimeType = st.GetContentType() + if st.IsBrowsableBinaryType() { + mimeType = st.GetMimeType() } else if isPlain { mimeType = "text/plain" } else { From 17480bd95f092be837819fd909ab50af4351573c Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 25 Jul 2022 20:10:07 +0200 Subject: [PATCH 10/15] use SplitN --- modules/typesniffer/typesniffer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index 48c4a1da70b96..76ab1a67ceb9b 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -77,7 +77,7 @@ func (ct SniffedType) IsBrowsableBinaryType() bool { // GetMimeType returns the mime type func (ct SniffedType) GetMimeType() string { - return ct.contentType[:strings.Index(ct.contentType, ";")] + return strings.SplitN(ct.contentType, ";", 2)[0]; } // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. From 50b1ec6e99f3359ac5b2672b073c5a7c0732783f Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 25 Jul 2022 20:28:22 +0200 Subject: [PATCH 11/15] fmt --- modules/typesniffer/typesniffer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index 76ab1a67ceb9b..e50928e8c260a 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -77,7 +77,7 @@ func (ct SniffedType) IsBrowsableBinaryType() bool { // GetMimeType returns the mime type func (ct SniffedType) GetMimeType() string { - return strings.SplitN(ct.contentType, ";", 2)[0]; + return strings.SplitN(ct.contentType, ";", 2)[0] } // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. From 388cfd3a835bd7420312433550f6e604d390e733 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 26 Jul 2022 13:45:11 +0200 Subject: [PATCH 12/15] Update routers/common/repo.go --- routers/common/repo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/common/repo.go b/routers/common/repo.go index 2cc3754bedb07..432a4a7e4e2ec 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -107,6 +107,7 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") } + // encode filename per https://datatracker.ietf.org/doc/html/rfc5987 ctx.Resp.Header().Set("Content-Disposition", `inline; filename*=UTF-8''`+url.PathEscape(fileName)) ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") From 9a443965ef33f5e29ba0bc49c4cc94d91ecbab34 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 26 Jul 2022 20:06:16 +0200 Subject: [PATCH 13/15] Update routers/common/repo.go --- routers/common/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 432a4a7e4e2ec..90542eedfa1cc 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -103,7 +103,7 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } else if st.IsPDF() { // no sandbox attribute for pdf as it breaks rendering in at least safari. this // should generally be safe as scripts inside PDF can not escape the PDF document - // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion + // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") } From b40db99221f2170680f596c0e445ad4cfa5b398c Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 26 Jul 2022 20:30:55 +0200 Subject: [PATCH 14/15] rename variables for clarity --- routers/common/repo.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index 90542eedfa1cc..a4155e8424eea 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/charset" + charsetModule "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" @@ -62,10 +62,10 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } fileName := path.Base(filePath) - st := typesniffer.DetectContentType(buf) - isPlain := st.IsText() || ctx.FormBool("render") + sniffedType := typesniffer.DetectContentType(buf) + isPlain := sniffedType.IsText() || ctx.FormBool("render") mimeType := "" - cs := "" + charset := "" if setting.MimeTypeMap.Enabled { fileExtension := strings.ToLower(filepath.Ext(fileName)) @@ -73,8 +73,8 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } if mimeType == "" { - if st.IsBrowsableBinaryType() { - mimeType = st.GetMimeType() + if sniffedType.IsBrowsableBinaryType() { + mimeType = sniffedType.GetMimeType() } else if isPlain { mimeType = "text/plain" } else { @@ -83,24 +83,24 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } if isPlain { - cs, err = charset.DetectEncoding(buf) + charset, err = charsetModule.DetectEncoding(buf) if err != nil { log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) - cs = "utf-8" + charset = "utf-8" } } - if cs != "" { - ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(cs)) + if charset != "" { + ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset)) } else { ctx.Resp.Header().Set("Content-Type", mimeType) } ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") // serve types that can present a security risk with CSP - if st.IsSvgImage() { + if sniffedType.IsSvgImage() { ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") - } else if st.IsPDF() { + } else if sniffedType.IsPDF() { // no sandbox attribute for pdf as it breaks rendering in at least safari. this // should generally be safe as scripts inside PDF can not escape the PDF document // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion From 4dceedd21147210f0af3153cc9531d3fdc74f404 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 27 Jul 2022 21:49:11 +0200 Subject: [PATCH 15/15] restore setting.UI.SVG.Enabled behaviour --- routers/common/repo.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/routers/common/repo.go b/routers/common/repo.go index a4155e8424eea..a9e80fad48c8d 100644 --- a/routers/common/repo.go +++ b/routers/common/repo.go @@ -97,8 +97,10 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read } ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") + isSVG := sniffedType.IsSvgImage() + // serve types that can present a security risk with CSP - if sniffedType.IsSvgImage() { + if isSVG { ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") } else if sniffedType.IsPDF() { // no sandbox attribute for pdf as it breaks rendering in at least safari. this @@ -107,8 +109,15 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") } + disposition := "inline" + if isSVG && !setting.UI.SVG.Enabled { + disposition = "attachment" + } + // encode filename per https://datatracker.ietf.org/doc/html/rfc5987 - ctx.Resp.Header().Set("Content-Disposition", `inline; filename*=UTF-8''`+url.PathEscape(fileName)) + encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName) + + ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName) ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") _, err = ctx.Resp.Write(buf)