chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package converter
|
||||
|
||||
func ArrayNotInArray(original []string, search []string) []string {
|
||||
var result []string
|
||||
originalMap := make(map[string]bool)
|
||||
for _, v := range original {
|
||||
originalMap[v] = true
|
||||
}
|
||||
for _, v := range search {
|
||||
if _, ok := originalMap[v]; !ok {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func UniqueArray[T comparable](input []T) []T {
|
||||
result := make([]T, 0, len(input))
|
||||
seen := make(map[T]bool, len(input))
|
||||
for _, element := range input {
|
||||
if !seen[element] {
|
||||
result = append(result, element)
|
||||
seen[element] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
goldmarkHTML "github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Markdown2HTML convert markdown to html
|
||||
func Markdown2HTML(source string) string {
|
||||
mdConverter := goldmark.New(
|
||||
goldmark.WithExtensions(&DangerousHTMLFilterExtension{}, extension.GFM, extension.Footnote),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
goldmarkHTML.WithHardWraps(),
|
||||
),
|
||||
)
|
||||
var buf bytes.Buffer
|
||||
if err := mdConverter.Convert([]byte(source), &buf); err != nil {
|
||||
log.Error(err)
|
||||
return source
|
||||
}
|
||||
html := buf.String()
|
||||
filter := bluemonday.UGCPolicy()
|
||||
filter.AllowStyling()
|
||||
filter.RequireNoFollowOnLinks(false)
|
||||
filter.RequireParseableURLs(false)
|
||||
filter.RequireNoFollowOnFullyQualifiedLinks(false)
|
||||
filter.AllowElements("kbd")
|
||||
filter.AllowAttrs("title").Matching(regexp.MustCompile(`^[\p{L}\p{N}\s\-_',\[\]!\./\\\(\)]*$|^@embed?$`)).Globally()
|
||||
filter.AllowAttrs("start").OnElements("ol")
|
||||
html = strings.TrimSpace(filter.Sanitize(html))
|
||||
return html
|
||||
}
|
||||
|
||||
// Markdown2BasicHTML convert markdown to html, Only basic syntax can be used
|
||||
func Markdown2BasicHTML(source string) string {
|
||||
content := Markdown2HTML(source)
|
||||
filter := bluemonday.NewPolicy()
|
||||
filter.AllowElements("p", "b", "br", "strong", "em")
|
||||
filter.AllowAttrs("src").OnElements("img")
|
||||
filter.AddSpaceWhenStrippingTag(true)
|
||||
content = filter.Sanitize(content)
|
||||
return content
|
||||
}
|
||||
|
||||
type DangerousHTMLFilterExtension struct {
|
||||
}
|
||||
|
||||
func (e *DangerousHTMLFilterExtension) Extend(m goldmark.Markdown) {
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(&DangerousHTMLRenderer{
|
||||
Config: goldmarkHTML.NewConfig(),
|
||||
Filter: bluemonday.UGCPolicy(),
|
||||
}, 1),
|
||||
))
|
||||
}
|
||||
|
||||
type DangerousHTMLRenderer struct {
|
||||
goldmarkHTML.Config
|
||||
Filter *bluemonday.Policy
|
||||
}
|
||||
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *DangerousHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
|
||||
reg.Register(ast.KindRawHTML, r.renderRawHTML)
|
||||
reg.Register(ast.KindLink, r.renderLink)
|
||||
reg.Register(ast.KindAutoLink, r.renderAutoLink)
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
n := node.(*ast.RawHTML)
|
||||
l := n.Segments.Len()
|
||||
for i := range l {
|
||||
segment := n.Segments.At(i)
|
||||
if string(source[segment.Start:segment.Stop]) == "<kbd>" || string(source[segment.Start:segment.Stop]) == "</kbd>" {
|
||||
_, _ = w.Write(segment.Value(source))
|
||||
} else {
|
||||
_, _ = w.Write(r.Filter.SanitizeBytes(segment.Value(source)))
|
||||
}
|
||||
}
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
n := node.(*ast.HTMLBlock)
|
||||
if entering {
|
||||
l := n.Lines().Len()
|
||||
for i := range l {
|
||||
line := n.Lines().At(i)
|
||||
r.Writer.SecureWrite(w, line.Value(source))
|
||||
}
|
||||
} else if n.HasClosure() {
|
||||
closure := n.ClosureLine
|
||||
r.Writer.SecureWrite(w, closure.Value(source))
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
n := node.(*ast.Link)
|
||||
if entering && r.renderLinkIsUrl(string(n.Destination)) {
|
||||
_, _ = w.WriteString("<a href=\"")
|
||||
// _, _ = w.WriteString("<a test=\"1\" rel=\"nofollow\" href=\"")
|
||||
if r.Unsafe || !goldmarkHTML.IsDangerousURL(n.Destination) {
|
||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||
}
|
||||
_ = w.WriteByte('"')
|
||||
if n.Title != nil {
|
||||
_, _ = w.WriteString(` title="`)
|
||||
r.Writer.Write(w, n.Title)
|
||||
_ = w.WriteByte('"')
|
||||
}
|
||||
if n.Attributes() != nil {
|
||||
goldmarkHTML.RenderAttributes(w, n, goldmarkHTML.LinkAttributeFilter)
|
||||
}
|
||||
_ = w.WriteByte('>')
|
||||
} else {
|
||||
_, _ = w.WriteString("</a>")
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
n := node.(*ast.AutoLink)
|
||||
|
||||
if !entering || !r.renderLinkIsUrl(string(n.URL(source))) {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
_, _ = w.WriteString(`<a href="`)
|
||||
url := n.URL(source)
|
||||
label := n.Label(source)
|
||||
if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
|
||||
_, _ = w.WriteString("mailto:")
|
||||
}
|
||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
|
||||
if n.Attributes() != nil {
|
||||
_ = w.WriteByte('"')
|
||||
goldmarkHTML.RenderAttributes(w, n, goldmarkHTML.LinkAttributeFilter)
|
||||
_ = w.WriteByte('>')
|
||||
} else {
|
||||
_, _ = w.WriteString(`">`)
|
||||
}
|
||||
_, _ = w.Write(util.EscapeHTML(label))
|
||||
_, _ = w.WriteString(`</a>`)
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderLinkIsUrl(verifyURL string) bool {
|
||||
isURL := govalidator.IsURL(verifyURL)
|
||||
isPath, _ := regexp.MatchString(`^/`, verifyURL)
|
||||
return isURL || isPath
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
func StringToInt64(str string) int64 {
|
||||
num, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
func StringToInt(str string) int {
|
||||
num, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
func IntToString(data int64) string {
|
||||
return fmt.Sprintf("%d", data)
|
||||
}
|
||||
|
||||
// InterfaceToString converts data to string
|
||||
// It will be used in template render
|
||||
func InterfaceToString(data any) string {
|
||||
switch t := data.(type) {
|
||||
case int:
|
||||
i := data.(int)
|
||||
return strconv.Itoa(i)
|
||||
case int8:
|
||||
i := data.(int8)
|
||||
return strconv.Itoa(int(i))
|
||||
case int16:
|
||||
i := data.(int16)
|
||||
return strconv.Itoa(int(i))
|
||||
case int32:
|
||||
i := data.(int32)
|
||||
return string(i)
|
||||
case int64:
|
||||
i := data.(int64)
|
||||
return strconv.FormatInt(i, 10)
|
||||
case string:
|
||||
return data.(string)
|
||||
default:
|
||||
log.Warn("can't convert type:", t)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/segmentfault/pacman/utils"
|
||||
)
|
||||
|
||||
func DeleteUserDisplay(userID string) string {
|
||||
return utils.EnShortID(StringToInt64(userID), 100)
|
||||
}
|
||||
|
||||
func GetMentionUsernameList(text string) []string {
|
||||
re := regexp.MustCompile(`\[@([^\]]+)\]\(/users/[^\)]+\)`)
|
||||
matches := re.FindAllStringSubmatch(text, -1)
|
||||
|
||||
var usernames []string
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 {
|
||||
usernames = append(usernames, match[1])
|
||||
}
|
||||
}
|
||||
return usernames
|
||||
}
|
||||
Reference in New Issue
Block a user