Lindenii Project Forge
Reduce allocations when converting string to []byte
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "encoding/hex" "errors" "fmt" "io" "net" "net/http" "strings" "git.sr.ht/~sircmpwn/go-bare" ) type commitDisplay struct { Hash string Author string Email string Date string Message string } // httpHandleRepoIndex provides the front page of a repo using git2d. func httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[string]any) { repoName := params["repo_name"].(string) groupPath := params["group_path"].([]string) _, repoPath, _, _, _, _, _ := getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo var notes []string if strings.Contains(repoName, "\n") || sliceContainsNewlines(groupPath) { notes = append(notes, "Path contains newlines; HTTP Git access impossible") } conn, err := net.Dial("unix", config.Git.Socket) if err != nil { errorPage500(w, params, "git2d connection failed: "+err.Error()) return } defer conn.Close() writer := bare.NewWriter(conn) reader := bare.NewReader(conn)
if err := writer.WriteData([]byte(repoPath)); err != nil {
if err := writer.WriteData(stringToBytes(repoPath)); err != nil {
errorPage500(w, params, "sending repo path failed: "+err.Error()) return } if err := writer.WriteUint(1); err != nil { errorPage500(w, params, "sending command failed: "+err.Error()) return } status, err := reader.ReadUint() if err != nil { errorPage500(w, params, "reading status failed: "+err.Error()) return } if status != 0 { errorPage500(w, params, fmt.Sprintf("git2d error: %d", status)) return } // README readmeRaw, err := reader.ReadData() if err != nil { readmeRaw = nil } readmeFilename, readmeRendered := renderReadme(readmeRaw, "README.md") // Commits var commits []commitDisplay for { id, err := reader.ReadData() if err != nil { if errors.Is(err, io.EOF) { break } errorPage500(w, params, "error reading commit ID: "+err.Error()) return } title, _ := reader.ReadData() authorName, _ := reader.ReadData() authorEmail, _ := reader.ReadData() authorDate, _ := reader.ReadData() commits = append(commits, commitDisplay{ Hash: hex.EncodeToString(id), Author: string(authorName), Email: string(authorEmail), Date: string(authorDate), Message: string(title), }) } params["commits"] = commits params["readme_filename"] = readmeFilename params["readme"] = readmeRendered params["notes"] = notes renderTemplate(w, "repo_index", params) // TODO: Caching }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "errors" "fmt" "html/template" "io" "net" "net/http" "strings" "git.sr.ht/~sircmpwn/go-bare" ) // httpHandleRepoRaw serves raw files, or directory listings that point to raw // files. func httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) { repoName := params["repo_name"].(string) groupPath := params["group_path"].([]string) rawPathSpec := params["rest"].(string) pathSpec := strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec _, repoPath, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "") conn, err := net.Dial("unix", config.Git.Socket) if err != nil { errorPage500(writer, params, "git2d connection failed: "+err.Error()) return } defer conn.Close() brWriter := bare.NewWriter(conn) brReader := bare.NewReader(conn)
if err := brWriter.WriteData([]byte(repoPath)); err != nil {
if err := brWriter.WriteData(stringToBytes(repoPath)); err != nil {
errorPage500(writer, params, "sending repo path failed: "+err.Error()) return } if err := brWriter.WriteUint(2); err != nil { errorPage500(writer, params, "sending command failed: "+err.Error()) return }
if err := brWriter.WriteData([]byte(pathSpec)); err != nil {
if err := brWriter.WriteData(stringToBytes(pathSpec)); err != nil {
errorPage500(writer, params, "sending path failed: "+err.Error()) return } status, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading status failed: "+err.Error()) return } switch status { case 0: kind, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading object kind failed: "+err.Error()) return } switch kind { case 1: // Tree if redirectDir(writer, request) { return } count, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading entry count failed: "+err.Error()) return } files := make([]displayTreeEntry, 0, count) for range count { typeCode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry type: "+err.Error()) return } mode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry mode: "+err.Error()) return } size, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry size: "+err.Error()) return } name, err := brReader.ReadData() if err != nil { errorPage500(writer, params, "error reading entry name: "+err.Error()) return } files = append(files, displayTreeEntry{ Name: string(name), Mode: fmt.Sprintf("%06o", mode), Size: size, IsFile: typeCode == 2, IsSubtree: typeCode == 1, }) } params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_raw_dir", params) case 2: // Blob if redirectNoDir(writer, request) { return } content, err := brReader.ReadData() if err != nil && !errors.Is(err, io.EOF) { errorPage500(writer, params, "error reading blob content: "+err.Error()) return } writer.Header().Set("Content-Type", "application/octet-stream") fmt.Fprint(writer, string(content)) default: errorPage500(writer, params, fmt.Sprintf("unknown object kind: %d", kind)) } case 3: errorPage500(writer, params, "path not found: "+pathSpec) default: errorPage500(writer, params, fmt.Sprintf("unknown status code: %d", status)) } }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "errors" "fmt" "html/template" "io" "net" "net/http" "strings" "git.sr.ht/~sircmpwn/go-bare" ) // httpHandleRepoTree provides a friendly, syntax-highlighted view of // individual files, and provides directory views that link to these files. // // TODO: Do not highlight files that are too large. func httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, params map[string]any) { repoName := params["repo_name"].(string) groupPath := params["group_path"].([]string) rawPathSpec := params["rest"].(string) pathSpec := strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec _, repoPath, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "") conn, err := net.Dial("unix", config.Git.Socket) if err != nil { errorPage500(writer, params, "git2d connection failed: "+err.Error()) return } defer conn.Close() brWriter := bare.NewWriter(conn) brReader := bare.NewReader(conn)
if err := brWriter.WriteData([]byte(repoPath)); err != nil {
if err := brWriter.WriteData(stringToBytes(repoPath)); err != nil {
errorPage500(writer, params, "sending repo path failed: "+err.Error()) return } if err := brWriter.WriteUint(2); err != nil { errorPage500(writer, params, "sending command failed: "+err.Error()) return }
if err := brWriter.WriteData([]byte(pathSpec)); err != nil {
if err := brWriter.WriteData(stringToBytes(pathSpec)); err != nil {
errorPage500(writer, params, "sending path failed: "+err.Error()) return } status, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading status failed: "+err.Error()) return } switch status { case 0: kind, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading object kind failed: "+err.Error()) return } switch kind { case 1: // Tree count, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "reading entry count failed: "+err.Error()) return } files := make([]displayTreeEntry, 0, count) for range count { typeCode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry type: "+err.Error()) return } mode, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry mode: "+err.Error()) return } size, err := brReader.ReadUint() if err != nil { errorPage500(writer, params, "error reading entry size: "+err.Error()) return } name, err := brReader.ReadData() if err != nil { errorPage500(writer, params, "error reading entry name: "+err.Error()) return } files = append(files, displayTreeEntry{ Name: string(name), Mode: fmt.Sprintf("%06o", mode), Size: size, IsFile: typeCode == 2, IsSubtree: typeCode == 1, }) } params["files"] = files params["readme_filename"] = "README.md" params["readme"] = template.HTML("<p>README rendering here is WIP again</p>") // TODO renderTemplate(writer, "repo_tree_dir", params) case 2: // Blob content, err := brReader.ReadData() if err != nil && !errors.Is(err, io.EOF) { errorPage500(writer, params, "error reading file content: "+err.Error()) return } rendered := renderHighlightedFile(pathSpec, string(content)) params["file_contents"] = rendered renderTemplate(writer, "repo_tree_file", params) default: errorPage500(writer, params, fmt.Sprintf("unknown kind: %d", kind)) return } case 3: errorPage500(writer, params, "path not found: "+pathSpec) return default: errorPage500(writer, params, fmt.Sprintf("unknown status code: %d", status)) } }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "bytes" "html" "html/template" "strings" "github.com/go-git/go-git/v5/plumbing/object" "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 } // renderReadmeAtTree looks for README files in the supplied Git tree and // returns its filename and rendered (and sanitized) HTML. func renderReadmeAtTree(tree *object.Tree) (string, template.HTML) { for _, name := range []string{"README", "README.md", "README.org"} { file, err := tree.File(name) if err != nil { continue } contents, err := file.Contents() if err != nil { return "Error fetching README", escapeHTML("Unable to fetch contents of " + name + ": " + err.Error()) }
return renderReadme([]byte(contents), name)
return renderReadme(stringToBytes(contents), name)
} return "", "" } // 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("<pre>" + html.EscapeString(string(data)) + "</pre>") //#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(string(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("<pre>" + html.EscapeString(string(data)) + "</pre>") //#nosec G203 } }