From 4f4f6a25be2625b4bb2cb10e3520f52c4a35c243 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 5 Apr 2025 18:26:51 +0800 Subject: Separate code/README rendering and unsafe to their own packages --- chroma.go | 35 ----------------------------------- git_hooks_handle_linux.go | 3 ++- git_hooks_handle_other.go | 3 ++- git_plumbing.go | 6 ++++-- http_handle_repo_index.go | 3 ++- http_handle_repo_tree.go | 3 ++- lmtp_handle_patch.go | 3 ++- misc/unsafe.go | 20 ++++++++++++++++++++ readme_to_html.go | 45 --------------------------------------------- render/chroma.go | 35 +++++++++++++++++++++++++++++++++++ render/escape.go | 11 +++++++++++ render/readme.go | 41 +++++++++++++++++++++++++++++++++++++++++ resources.go | 3 ++- ssh_server.go | 5 +++-- unsafe.go | 20 -------------------- 15 files changed, 126 insertions(+), 110 deletions(-) delete mode 100644 chroma.go create mode 100644 misc/unsafe.go delete mode 100644 readme_to_html.go create mode 100644 render/chroma.go create mode 100644 render/escape.go create mode 100644 render/readme.go delete mode 100644 unsafe.go diff --git a/chroma.go b/chroma.go deleted file mode 100644 index 0d904b7..0000000 --- a/chroma.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "bytes" - "html/template" - - chromaHTML "github.com/alecthomas/chroma/v2/formatters/html" - chromaLexers "github.com/alecthomas/chroma/v2/lexers" - chromaStyles "github.com/alecthomas/chroma/v2/styles" -) - -func renderHighlightedFile(filename, content string) template.HTML { - lexer := chromaLexers.Match(filename) - if lexer == nil { - lexer = chromaLexers.Fallback - } - - iterator, err := lexer.Tokenise(nil, content) - if err != nil { - return template.HTML("
Error tokenizing file: " + err.Error() + "
") //#nosec G203` - } - - var buf bytes.Buffer - style := chromaStyles.Get("autumn") - formatter := chromaHTML.New( - chromaHTML.WithClasses(true), - chromaHTML.TabWidth(8), - ) - - if err := formatter.Format(&buf, style, iterator); err != nil { - return template.HTML("
Error formatting file: " + err.Error() + "
") //#nosec G203 - } - - return template.HTML(buf.Bytes()) //#nosec G203 -} diff --git a/git_hooks_handle_linux.go b/git_hooks_handle_linux.go index e316bb7..3c556c3 100644 --- a/git_hooks_handle_linux.go +++ b/git_hooks_handle_linux.go @@ -22,6 +22,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/jackc/pgx/v5" + "go.lindenii.runxiyu.org/forge/misc" "go.lindenii.runxiyu.org/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" ) @@ -76,7 +77,7 @@ func hooksHandler(conn net.Conn) { { var ok bool - packPass, ok = packPasses.Load(bytesToString(cookie)) + packPass, ok = packPasses.Load(misc.BytesToString(cookie)) if !ok { if _, err = conn.Write([]byte{1}); err != nil { return diff --git a/git_hooks_handle_other.go b/git_hooks_handle_other.go index 4285784..89a4193 100644 --- a/git_hooks_handle_other.go +++ b/git_hooks_handle_other.go @@ -20,6 +20,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/jackc/pgx/v5" + "go.lindenii.runxiyu.org/forge/misc" "go.lindenii.runxiyu.org/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" ) @@ -54,7 +55,7 @@ func hooksHandler(conn net.Conn) { { var ok bool - packPass, ok = packPasses.Load(bytesToString(cookie)) + packPass, ok = packPasses.Load(misc.BytesToString(cookie)) if !ok { if _, err = conn.Write([]byte{1}); err != nil { return diff --git a/git_plumbing.go b/git_plumbing.go index 15329ad..74c80ac 100644 --- a/git_plumbing.go +++ b/git_plumbing.go @@ -13,6 +13,8 @@ import ( "path" "sort" "strings" + + "go.lindenii.runxiyu.org/forge/misc" ) func writeTree(ctx context.Context, repoPath string, entries []treeEntry) (string, error) { @@ -76,14 +78,14 @@ func buildTreeRecursive(ctx context.Context, repoPath, baseTree string, updates if modeEnd < 0 { return errors.New("invalid tree format") } - mode := bytesToString(data[i : i+modeEnd]) + mode := misc.BytesToString(data[i : i+modeEnd]) i += modeEnd + 1 nameEnd := bytes.IndexByte(data[i:], 0) if nameEnd < 0 { return errors.New("missing null after filename") } - name := bytesToString(data[i : i+nameEnd]) + name := misc.BytesToString(data[i : i+nameEnd]) i += nameEnd + 1 if i+20 > len(data) { diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go index 182b5df..ef1b76e 100644 --- a/http_handle_repo_index.go +++ b/http_handle_repo_index.go @@ -8,6 +8,7 @@ import ( "strings" "go.lindenii.runxiyu.org/forge/git2c" + "go.lindenii.runxiyu.org/forge/render" ) type commitDisplay struct { @@ -45,7 +46,7 @@ func httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[st params["commits"] = commits params["readme_filename"] = readme.Filename - _, params["readme"] = renderReadme(readme.Content, readme.Filename) + _, params["readme"] = render.Readme(readme.Content, readme.Filename) params["notes"] = notes renderTemplate(w, "repo_index", params) diff --git a/http_handle_repo_tree.go b/http_handle_repo_tree.go index 889406b..9cdd9cd 100644 --- a/http_handle_repo_tree.go +++ b/http_handle_repo_tree.go @@ -9,6 +9,7 @@ import ( "strings" "go.lindenii.runxiyu.org/forge/git2c" + "go.lindenii.runxiyu.org/forge/render" ) // httpHandleRepoTree provides a friendly, syntax-highlighted view of @@ -44,7 +45,7 @@ func httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, param params["readme"] = template.HTML("

README rendering here is WIP again

") // TODO renderTemplate(writer, "repo_tree_dir", params) case content != "": - rendered := renderHighlightedFile(pathSpec, content) + rendered := render.Highlight(pathSpec, content) params["file_contents"] = rendered renderTemplate(writer, "repo_tree_file", params) default: diff --git a/lmtp_handle_patch.go b/lmtp_handle_patch.go index 6bcb272..45d146a 100644 --- a/lmtp_handle_patch.go +++ b/lmtp_handle_patch.go @@ -16,6 +16,7 @@ import ( "github.com/bluekeyes/go-gitdiff/gitdiff" "github.com/go-git/go-git/v5" + "go.lindenii.runxiyu.org/forge/misc" ) func lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, mbox io.Reader) (err error) { @@ -63,7 +64,7 @@ func lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, return fmt.Errorf("failed to get contents: %w", err) } - sourceBuf := bytes.NewReader(stringToBytes(sourceString)) + sourceBuf := bytes.NewReader(misc.StringToBytes(sourceString)) var patchedBuf bytes.Buffer if err := gitdiff.Apply(&patchedBuf, sourceBuf, diffFile); err != nil { return fmt.Errorf("failed to apply patch: %w", err) diff --git a/misc/unsafe.go b/misc/unsafe.go new file mode 100644 index 0000000..6c2192f --- /dev/null +++ b/misc/unsafe.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu + +package misc + +import "unsafe" + +// StringToBytes converts a string to a byte slice without copying the string. +// Memory is borrowed from the string. +// The resulting byte slice must not be modified in any form. +func StringToBytes(s string) (bytes []byte) { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +// BytesToString converts a byte slice to a string without copying the bytes. +// Memory is borrowed from the byte slice. +// The source byte slice must not be modified. +func BytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} diff --git a/readme_to_html.go b/readme_to_html.go deleted file mode 100644 index 6d3f6f3..0000000 --- a/readme_to_html.go +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu - -package main - -import ( - "bytes" - "html" - "html/template" - "strings" - - "github.com/microcosm-cc/bluemonday" - "github.com/niklasfasching/go-org/org" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" -) - -var markdownConverter = goldmark.New(goldmark.WithExtensions(extension.GFM)) - -// escapeHTML just escapes a string and wraps it in [template.HTML]. -func escapeHTML(s string) template.HTML { - return template.HTML(html.EscapeString(s)) //#nosec G203 -} - -// renderReadme renders and sanitizes README content from a byte slice and filename. -func renderReadme(data []byte, filename string) (string, template.HTML) { - switch strings.ToLower(filename) { - case "readme": - return "README", template.HTML("
" + html.EscapeString(bytesToString(data)) + "
") //#nosec G203 - case "readme.md": - var buf bytes.Buffer - if err := markdownConverter.Convert(data, &buf); err != nil { - return "Error fetching README", escapeHTML("Unable to render README: " + err.Error()) - } - return "README.md", template.HTML(bluemonday.UGCPolicy().SanitizeBytes(buf.Bytes())) //#nosec G203 - case "readme.org": - htmlStr, err := org.New().Parse(strings.NewReader(bytesToString(data)), filename).Write(org.NewHTMLWriter()) - if err != nil { - return "Error fetching README", escapeHTML("Unable to render README: " + err.Error()) - } - return "README.org", template.HTML(bluemonday.UGCPolicy().Sanitize(htmlStr)) //#nosec G203 - default: - return filename, template.HTML("
" + html.EscapeString(bytesToString(data)) + "
") //#nosec G203 - } -} diff --git a/render/chroma.go b/render/chroma.go new file mode 100644 index 0000000..c7d64ec --- /dev/null +++ b/render/chroma.go @@ -0,0 +1,35 @@ +package render + +import ( + "bytes" + "html/template" + + chromaHTML "github.com/alecthomas/chroma/v2/formatters/html" + chromaLexers "github.com/alecthomas/chroma/v2/lexers" + chromaStyles "github.com/alecthomas/chroma/v2/styles" +) + +func Highlight(filename, content string) template.HTML { + lexer := chromaLexers.Match(filename) + if lexer == nil { + lexer = chromaLexers.Fallback + } + + iterator, err := lexer.Tokenise(nil, content) + if err != nil { + return template.HTML("
Error tokenizing file: " + err.Error() + "
") //#nosec G203` + } + + var buf bytes.Buffer + style := chromaStyles.Get("autumn") + formatter := chromaHTML.New( + chromaHTML.WithClasses(true), + chromaHTML.TabWidth(8), + ) + + if err := formatter.Format(&buf, style, iterator); err != nil { + return template.HTML("
Error formatting file: " + err.Error() + "
") //#nosec G203 + } + + return template.HTML(buf.Bytes()) //#nosec G203 +} diff --git a/render/escape.go b/render/escape.go new file mode 100644 index 0000000..44c56f3 --- /dev/null +++ b/render/escape.go @@ -0,0 +1,11 @@ +package render + +import ( + "html" + "html/template" +) + +// EscapeHTML just escapes a string and wraps it in [template.HTML]. +func EscapeHTML(s string) template.HTML { + return template.HTML(html.EscapeString(s)) //#nosec G203 +} diff --git a/render/readme.go b/render/readme.go new file mode 100644 index 0000000..1a153fb --- /dev/null +++ b/render/readme.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu + +package render + +import ( + "bytes" + "html" + "html/template" + "strings" + + "github.com/microcosm-cc/bluemonday" + "github.com/niklasfasching/go-org/org" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "go.lindenii.runxiyu.org/forge/misc" +) + +var markdownConverter = goldmark.New(goldmark.WithExtensions(extension.GFM)) + +// renderReadme renders and sanitizes README content from a byte slice and filename. +func Readme(data []byte, filename string) (string, template.HTML) { + switch strings.ToLower(filename) { + case "readme": + return "README", template.HTML("
" + html.EscapeString(misc.BytesToString(data)) + "
") //#nosec G203 + case "readme.md": + var buf bytes.Buffer + if err := markdownConverter.Convert(data, &buf); err != nil { + return "Error fetching README", EscapeHTML("Unable to render README: " + err.Error()) + } + return "README.md", template.HTML(bluemonday.UGCPolicy().SanitizeBytes(buf.Bytes())) //#nosec G203 + case "readme.org": + htmlStr, err := org.New().Parse(strings.NewReader(misc.BytesToString(data)), filename).Write(org.NewHTMLWriter()) + if err != nil { + return "Error fetching README", EscapeHTML("Unable to render README: " + err.Error()) + } + return "README.org", template.HTML(bluemonday.UGCPolicy().Sanitize(htmlStr)) //#nosec G203 + default: + return filename, template.HTML("
" + html.EscapeString(misc.BytesToString(data)) + "
") //#nosec G203 + } +} diff --git a/resources.go b/resources.go index 0bb033a..00d7b66 100644 --- a/resources.go +++ b/resources.go @@ -11,6 +11,7 @@ import ( "github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2/html" + "go.lindenii.runxiyu.org/forge/misc" ) //go:embed LICENSE source.tar.gz @@ -59,7 +60,7 @@ func loadTemplates() (err error) { return err } - _, err = templates.Parse(bytesToString(minified)) + _, err = templates.Parse(misc.BytesToString(minified)) if err != nil { return err } diff --git a/ssh_server.go b/ssh_server.go index a3b69e4..1408a2a 100644 --- a/ssh_server.go +++ b/ssh_server.go @@ -10,6 +10,7 @@ import ( "strings" gliderSSH "github.com/gliderlabs/ssh" + "go.lindenii.runxiyu.org/forge/misc" "go.lindenii.runxiyu.org/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" goSSH "golang.org/x/crypto/ssh" @@ -39,7 +40,7 @@ func serveSSH(listener net.Listener) error { } serverPubkey = hostKey.PublicKey() - serverPubkeyString = bytesToString(goSSH.MarshalAuthorizedKey(serverPubkey)) + serverPubkeyString = misc.BytesToString(goSSH.MarshalAuthorizedKey(serverPubkey)) serverPubkeyFP = goSSH.FingerprintSHA256(serverPubkey) server = &gliderSSH.Server{ @@ -47,7 +48,7 @@ func serveSSH(listener net.Listener) error { clientPubkey := session.PublicKey() var clientPubkeyStr string if clientPubkey != nil { - clientPubkeyStr = strings.TrimSuffix(bytesToString(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n") + clientPubkeyStr = strings.TrimSuffix(misc.BytesToString(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n") } clog.Info("Incoming SSH: " + session.RemoteAddr().String() + " " + clientPubkeyStr + " " + session.RawCommand()) diff --git a/unsafe.go b/unsafe.go deleted file mode 100644 index a4e9ac8..0000000 --- a/unsafe.go +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu - -package main - -import "unsafe" - -// stringToBytes converts a string to a byte slice without copying the string. -// Memory is borrowed from the string. -// The resulting byte slice must not be modified in any form. -func stringToBytes(s string) (bytes []byte) { - return unsafe.Slice(unsafe.StringData(s), len(s)) -} - -// bytesToString converts a byte slice to a string without copying the bytes. -// Memory is borrowed from the byte slice. -// The source byte slice must not be modified. -func bytesToString(b []byte) string { - return unsafe.String(unsafe.SliceData(b), len(b)) -} -- cgit v1.2.3