1
0
Fork 0
forked from forgejo/forgejo

Display SVG files as images instead of text (#14101)

* Change to display SVG files as images

* Remove unsafe styles from SVG CSP

* Add integration test to test SVG headers

* Add config setting to disable SVG rendering

* Add test for img tag when loading SVG image

* Remove the Raw view button for svg files since we don't fully support this

* Fix copyright year

* Rename and move config setting

* Add setting to cheat sheet in docs

* Fix so that comment matches cheat sheet

* Add allowing styles in CSP based on pull request feedback

* Re-enable raw button since we show SVG styles now

* Change so that SVG files are editable

* Add UI to toggle between source and rendered image for SVGs

* Change to show blame button for SVG images

* Fix to update ctx data

* Add test for DetectContentType when file is longer than sniffLen

Co-authored-by: Jonathan Tran <jon@allspice.io>
Co-authored-by: Kyle D <kdumontnu@gmail.com>
This commit is contained in:
Jonathan Tran 2021-01-12 22:45:19 -05:00 committed by GitHub
parent 9465e60504
commit 81467e6f35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 736 additions and 16 deletions

View file

@ -15,6 +15,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@ -28,6 +29,15 @@ import (
"github.com/dustin/go-humanize"
)
// Use at most this many bytes to determine Content Type.
const sniffLen = 512
// SVGMimeType MIME type of SVG images.
const SVGMimeType = "image/svg+xml"
var svgTagRegex = regexp.MustCompile(`(?s)\A\s*(?:<!--.*?-->\s*)*<svg\b`)
var svgTagInXMLRegex = regexp.MustCompile(`(?s)\A<\?xml\b.*?\?>\s*(?:<!--.*?-->\s*)*<svg\b`)
// EncodeMD5 encodes string to md5 hex value.
func EncodeMD5(str string) string {
m := md5.New()
@ -265,32 +275,61 @@ func IsLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
}
// DetectContentType extends http.DetectContentType with more content types.
func DetectContentType(data []byte) string {
ct := http.DetectContentType(data)
if len(data) > sniffLen {
data = data[:sniffLen]
}
if setting.UI.SVG.Enabled &&
((strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")) && svgTagRegex.Match(data) ||
strings.Contains(ct, "text/xml") && svgTagInXMLRegex.Match(data)) {
// SVG is unsupported. https://github.com/golang/go/issues/15888
return SVGMimeType
}
return ct
}
// IsRepresentableAsText returns true if file content can be represented as
// plain text or is empty.
func IsRepresentableAsText(data []byte) bool {
return IsTextFile(data) || IsSVGImageFile(data)
}
// IsTextFile returns true if file content format is plain text or empty.
func IsTextFile(data []byte) bool {
if len(data) == 0 {
return true
}
return strings.Contains(http.DetectContentType(data), "text/")
return strings.Contains(DetectContentType(data), "text/")
}
// IsImageFile detects if data is an image format
func IsImageFile(data []byte) bool {
return strings.Contains(http.DetectContentType(data), "image/")
return strings.Contains(DetectContentType(data), "image/")
}
// IsSVGImageFile detects if data is an SVG image format
func IsSVGImageFile(data []byte) bool {
return strings.Contains(DetectContentType(data), SVGMimeType)
}
// IsPDFFile detects if data is a pdf format
func IsPDFFile(data []byte) bool {
return strings.Contains(http.DetectContentType(data), "application/pdf")
return strings.Contains(DetectContentType(data), "application/pdf")
}
// IsVideoFile detects if data is an video format
func IsVideoFile(data []byte) bool {
return strings.Contains(http.DetectContentType(data), "video/")
return strings.Contains(DetectContentType(data), "video/")
}
// IsAudioFile detects if data is an video format
func IsAudioFile(data []byte) bool {
return strings.Contains(http.DetectContentType(data), "audio/")
return strings.Contains(DetectContentType(data), "audio/")
}
// EntryIcon returns the octicon class for displaying files/directories