From 4f4f6a25be2625b4bb2cb10e3520f52c4a35c243 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 05 Apr 2025 18:26:51 +0800 Subject: [PATCH] Separate code/README rendering and unsafe to their own packages --- chroma.go => render/chroma.go | 4 ++-- 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 ++- readme_to_html.go | 45 --------------------------------------------- render/escape.go | 11 +++++++++++ render/readme.go | 41 +++++++++++++++++++++++++++++++++++++++++ resources.go | 3 ++- ssh_server.go | 5 +++-- unsafe.go => misc/unsafe.go | 10 +++++----- diff --git a/chroma.go b/render/chroma.go rename from chroma.go rename to render/chroma.go index 0d904b764fb811d40a5c79fd4bbc0e946bde7199..c7d64ec6f41126757dd3532a0a720327595ec2fa 100644 --- a/chroma.go +++ b/render/chroma.go @@ -1,4 +1,4 @@ -package main +package render import ( "bytes" @@ -9,7 +9,7 @@ chromaLexers "github.com/alecthomas/chroma/v2/lexers" chromaStyles "github.com/alecthomas/chroma/v2/styles" ) -func renderHighlightedFile(filename, content string) template.HTML { +func Highlight(filename, content string) template.HTML { lexer := chromaLexers.Match(filename) if lexer == nil { lexer = chromaLexers.Fallback diff --git a/git_hooks_handle_linux.go b/git_hooks_handle_linux.go index e316bb7ab6fb6e42f03e97084f425adee3704465..3c556c3ab95d7feb87bca5c606bcdcfdf5e32f53 100644 --- a/git_hooks_handle_linux.go +++ b/git_hooks_handle_linux.go @@ -22,6 +22,7 @@ "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 @@ } { 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 428578492ea6bb72074cfd8f91a2230d1037bd53..89a4193e4637d671736ece7e2eebcab673535c45 100644 --- a/git_hooks_handle_other.go +++ b/git_hooks_handle_other.go @@ -20,6 +20,7 @@ "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 @@ } { 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 15329adb4126a7df38f201ae896b66a8d4b1b8e9..74c80ac982a140d5e8793406f6726a5b692a67d5 100644 --- a/git_plumbing.go +++ b/git_plumbing.go @@ -13,6 +13,8 @@ "os/exec" "path" "sort" "strings" + + "go.lindenii.runxiyu.org/forge/misc" ) func writeTree(ctx context.Context, repoPath string, entries []treeEntry) (string, error) { @@ -76,14 +78,14 @@ modeEnd := bytes.IndexByte(data[i:], ' ') 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 182b5dffcb9eb80ba2cb2a77b114f57f7d5fbdb7..ef1b76ec09336173a27cef1b21f2181c7a5c6eac 100644 --- a/http_handle_repo_index.go +++ b/http_handle_repo_index.go @@ -8,6 +8,7 @@ "net/http" "strings" "go.lindenii.runxiyu.org/forge/git2c" + "go.lindenii.runxiyu.org/forge/render" ) type commitDisplay struct { @@ -45,7 +46,7 @@ } 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 889406bade45ee7d8b908be6ffed75027fa9a982..9cdd9cd332b0d44081319fc6a07de1279b4df55e 100644 --- a/http_handle_repo_tree.go +++ b/http_handle_repo_tree.go @@ -9,6 +9,7 @@ "net/http" "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 @@ params["readme_filename"] = "README.md" 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 6bcb272825be94bfd7ffc2a0d67d8a16599e4188..45d146a74d093e72fab244e3e9e456aaff8f8813 100644 --- a/lmtp_handle_patch.go +++ b/lmtp_handle_patch.go @@ -16,6 +16,7 @@ "time" "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 @@ if err != nil { 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/readme_to_html.go b/readme_to_html.go deleted file mode 100644 index 6d3f6f3e50fd46cfb5d7686095168ec88683280c..0000000000000000000000000000000000000000 --- 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/escape.go b/render/escape.go new file mode 100644 index 0000000000000000000000000000000000000000..44c56f3f8ff62d3342c64d7d11ba1a7627730c64 --- /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 0000000000000000000000000000000000000000..1a153fb317082e51118bce53b30ae55a2d2628d1 --- /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 0bb033ae2eda1c890c83b80b6abe848ce734df6e..00d7b66c4b242bad8eb940610cb616b7a9f9dd72 100644 --- a/resources.go +++ b/resources.go @@ -11,6 +11,7 @@ "net/http" "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 @@ if err != nil { 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 a3b69e461bd808486d075a83ee68b2f6834d7627..1408a2a02177f972b642d54e23ee9805eb5d95ff 100644 --- a/ssh_server.go +++ b/ssh_server.go @@ -10,6 +10,7 @@ "os" "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 @@ return err } 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 @@ Handler: func(session gliderSSH.Session) { 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/misc/unsafe.go rename from unsafe.go rename to misc/unsafe.go index a4e9ac8c39e872e17160220ac02d8fae9cec1460..6c2192f9af4b393fab52fef65a518b183b24aab0 100644 --- a/unsafe.go +++ b/misc/unsafe.go @@ -1,20 +1,20 @@ // SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu -package main +package misc import "unsafe" -// stringToBytes converts a string to a byte slice without copying the string. +// 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) { +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. +// 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 { +func BytesToString(b []byte) string { return unsafe.String(unsafe.SliceData(b), len(b)) } -- 2.48.1