From bd984fc238db643245f86accc2f03389289bcb58 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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 <me@silverwind.io>
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)