forked from jshiffer/matterbridge
265 lines
4.3 KiB
Go
265 lines
4.3 KiB
Go
|
// Copyright 2015 The Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package markdown
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
"unicode"
|
||
|
|
||
|
"gitlab.com/golang-commonmark/html"
|
||
|
"gitlab.com/golang-commonmark/mdurl"
|
||
|
"gitlab.com/golang-commonmark/puny"
|
||
|
)
|
||
|
|
||
|
func runeIsSpace(r rune) bool {
|
||
|
return r == ' ' || r == '\t'
|
||
|
}
|
||
|
|
||
|
func byteIsSpace(b byte) bool {
|
||
|
return b == ' ' || b == '\t'
|
||
|
}
|
||
|
|
||
|
func isLetter(b byte) bool {
|
||
|
return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z'
|
||
|
}
|
||
|
|
||
|
func isUppercaseLetter(b byte) bool {
|
||
|
return b >= 'A' && b <= 'Z'
|
||
|
}
|
||
|
|
||
|
func mdpunct(b byte) bool {
|
||
|
return strings.IndexByte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", b) != -1
|
||
|
}
|
||
|
|
||
|
func isMdAsciiPunct(r rune) bool {
|
||
|
if r > 0x7e {
|
||
|
return false
|
||
|
}
|
||
|
return mdpunct(byte(r))
|
||
|
}
|
||
|
|
||
|
func recodeHostnameFor(proto string) bool {
|
||
|
switch proto {
|
||
|
case "http", "https", "mailto":
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func normalizeLink(url string) string {
|
||
|
parsed, err := mdurl.Parse(url)
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
if parsed.Host != "" && (parsed.Scheme == "" || recodeHostnameFor(parsed.Scheme)) {
|
||
|
parsed.Host = puny.ToASCII(parsed.Host)
|
||
|
}
|
||
|
|
||
|
parsed.Scheme = parsed.RawScheme
|
||
|
|
||
|
return mdurl.Encode(parsed.String())
|
||
|
}
|
||
|
|
||
|
func normalizeLinkText(url string) string {
|
||
|
parsed, err := mdurl.Parse(url)
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
if parsed.Host != "" && (parsed.Scheme == "" || recodeHostnameFor(parsed.Scheme)) {
|
||
|
parsed.Host = puny.ToUnicode(parsed.Host)
|
||
|
}
|
||
|
|
||
|
parsed.Scheme = parsed.RawScheme
|
||
|
|
||
|
return mdurl.Decode(parsed.String())
|
||
|
}
|
||
|
|
||
|
var badProtos = []string{"file", "javascript", "vbscript"}
|
||
|
|
||
|
var rGoodData = regexp.MustCompile(`^data:image/(gif|png|jpeg|webp);`)
|
||
|
|
||
|
func removeSpecial(s string) string {
|
||
|
i := 0
|
||
|
for i < len(s) && !(s[i] <= 0x20 || s[i] == 0x7f) {
|
||
|
i++
|
||
|
}
|
||
|
if i >= len(s) {
|
||
|
return s
|
||
|
}
|
||
|
buf := make([]byte, len(s))
|
||
|
j := 0
|
||
|
for i := 0; i < len(s); i++ {
|
||
|
if !(s[i] <= 0x20 || s[i] == 0x7f) {
|
||
|
buf[j] = s[i]
|
||
|
j++
|
||
|
}
|
||
|
}
|
||
|
return string(buf[:j])
|
||
|
}
|
||
|
|
||
|
func validateLink(url string) bool {
|
||
|
str := strings.TrimSpace(url)
|
||
|
str = strings.ToLower(str)
|
||
|
|
||
|
if strings.IndexByte(str, ':') >= 0 {
|
||
|
proto := strings.SplitN(str, ":", 2)[0]
|
||
|
proto = removeSpecial(proto)
|
||
|
for _, p := range badProtos {
|
||
|
if proto == p {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
if proto == "data" && !rGoodData.MatchString(str) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func unescapeAll(s string) string {
|
||
|
anyChanges := false
|
||
|
i := 0
|
||
|
for i < len(s)-1 {
|
||
|
b := s[i]
|
||
|
if b == '\\' {
|
||
|
if mdpunct(s[i+1]) {
|
||
|
anyChanges = true
|
||
|
break
|
||
|
}
|
||
|
} else if b == '&' {
|
||
|
if e, n := html.ParseEntity(s[i:]); n > 0 && e != html.BadEntity {
|
||
|
anyChanges = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
i++
|
||
|
}
|
||
|
|
||
|
if !anyChanges {
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
buf := make([]byte, len(s))
|
||
|
copy(buf[:i], s)
|
||
|
j := i
|
||
|
for i < len(s) {
|
||
|
b := s[i]
|
||
|
if b == '\\' {
|
||
|
if i+1 < len(s) {
|
||
|
b = s[i+1]
|
||
|
if mdpunct(b) {
|
||
|
buf[j] = b
|
||
|
j++
|
||
|
} else {
|
||
|
buf[j] = '\\'
|
||
|
j++
|
||
|
buf[j] = b
|
||
|
j++
|
||
|
}
|
||
|
i += 2
|
||
|
continue
|
||
|
}
|
||
|
} else if b == '&' {
|
||
|
if e, n := html.ParseEntity(s[i:]); n > 0 && e != html.BadEntity {
|
||
|
if len(e) > n && len(buf) == len(s) {
|
||
|
newBuf := make([]byte, cap(buf)*2)
|
||
|
copy(newBuf[:j], buf)
|
||
|
buf = newBuf
|
||
|
}
|
||
|
j += copy(buf[j:], e)
|
||
|
i += n
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
buf[j] = b
|
||
|
j++
|
||
|
i++
|
||
|
}
|
||
|
|
||
|
return string(buf[:j])
|
||
|
}
|
||
|
|
||
|
func normalizeInlineCode(s string) string {
|
||
|
if s == "" {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
byteFuckery := false
|
||
|
i := 0
|
||
|
for i < len(s)-1 {
|
||
|
b := s[i]
|
||
|
if b == '\n' {
|
||
|
byteFuckery = true
|
||
|
break
|
||
|
}
|
||
|
if b == ' ' {
|
||
|
i++
|
||
|
b = s[i]
|
||
|
if b == ' ' || b == '\n' {
|
||
|
i--
|
||
|
byteFuckery = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
i++
|
||
|
}
|
||
|
|
||
|
if !byteFuckery {
|
||
|
return strings.TrimSpace(s)
|
||
|
}
|
||
|
|
||
|
buf := make([]byte, len(s))
|
||
|
copy(buf[:i], s)
|
||
|
buf[i] = ' '
|
||
|
i++
|
||
|
j := i
|
||
|
lastSpace := true
|
||
|
for i < len(s) {
|
||
|
b := s[i]
|
||
|
switch b {
|
||
|
case ' ', '\n':
|
||
|
if lastSpace {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
buf[j] = ' '
|
||
|
lastSpace = true
|
||
|
j++
|
||
|
default:
|
||
|
buf[j] = b
|
||
|
lastSpace = false
|
||
|
j++
|
||
|
}
|
||
|
|
||
|
i++
|
||
|
}
|
||
|
|
||
|
return string(bytes.TrimSpace(buf[:j]))
|
||
|
}
|
||
|
|
||
|
func normalizeReference(s string) string {
|
||
|
var buf bytes.Buffer
|
||
|
lastSpace := false
|
||
|
for _, r := range s {
|
||
|
if unicode.IsSpace(r) {
|
||
|
if !lastSpace {
|
||
|
buf.WriteByte(' ')
|
||
|
lastSpace = true
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
buf.WriteRune(unicode.ToLower(r))
|
||
|
lastSpace = false
|
||
|
}
|
||
|
|
||
|
return string(bytes.TrimSpace(buf.Bytes()))
|
||
|
}
|