Lindenii Project Forge
Reduce unnecessary allocations when converting []byte to string
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package main import ( "bytes" "context" "encoding/hex" "errors" "os" "os/exec" "path" "sort" "strings" ) func writeTree(ctx context.Context, repoPath string, entries []treeEntry) (string, error) { var buf bytes.Buffer sort.Slice(entries, func(i, j int) bool { nameI, nameJ := entries[i].name, entries[j].name if nameI == nameJ { // meh return !(entries[i].mode == "40000") && (entries[j].mode == "40000") } if strings.HasPrefix(nameJ, nameI) && len(nameI) < len(nameJ) { return !(entries[i].mode == "40000") } if strings.HasPrefix(nameI, nameJ) && len(nameJ) < len(nameI) { return entries[j].mode == "40000" } return nameI < nameJ }) for _, e := range entries { buf.WriteString(e.mode) buf.WriteByte(' ') buf.WriteString(e.name) buf.WriteByte(0) buf.Write(e.sha) } cmd := exec.CommandContext(ctx, "git", "hash-object", "-w", "-t", "tree", "--stdin") cmd.Env = append(os.Environ(), "GIT_DIR="+repoPath) cmd.Stdin = &buf var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { return "", err } return strings.TrimSpace(out.String()), nil } func buildTreeRecursive(ctx context.Context, repoPath, baseTree string, updates map[string][]byte) (string, error) { treeCache := make(map[string][]treeEntry) var walk func(string, string) error walk = func(prefix, sha string) error { cmd := exec.CommandContext(ctx, "git", "cat-file", "tree", sha) cmd.Env = append(os.Environ(), "GIT_DIR="+repoPath) var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { return err } data := out.Bytes() i := 0 var entries []treeEntry for i < len(data) { modeEnd := bytes.IndexByte(data[i:], ' ') if modeEnd < 0 { return errors.New("invalid tree format") }
mode := string(data[i : i+modeEnd])
mode := 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 := string(data[i : i+nameEnd])
name := bytesToString(data[i : i+nameEnd])
i += nameEnd + 1 if i+20 > len(data) { return errors.New("unexpected EOF in SHA") } shaBytes := data[i : i+20] i += 20 entries = append(entries, treeEntry{ mode: mode, name: name, sha: shaBytes, }) if mode == "40000" { subPrefix := path.Join(prefix, name) if err := walk(subPrefix, hex.EncodeToString(shaBytes)); err != nil { return err } } } treeCache[prefix] = entries return nil } if err := walk("", baseTree); err != nil { return "", err } for filePath, blobSha := range updates { parts := strings.Split(filePath, "/") dir := strings.Join(parts[:len(parts)-1], "/") name := parts[len(parts)-1] entries := treeCache[dir] found := false for i, e := range entries { if e.name == name { if blobSha == nil { // Remove TODO entries = append(entries[:i], entries[i+1:]...) } else { entries[i].sha = blobSha } found = true break } } if !found && blobSha != nil { entries = append(entries, treeEntry{ mode: "100644", name: name, sha: blobSha, }) } treeCache[dir] = entries } built := make(map[string][]byte) var build func(string) ([]byte, error) build = func(prefix string) ([]byte, error) { entries := treeCache[prefix] for i, e := range entries { if e.mode == "40000" { subPrefix := path.Join(prefix, e.name) if sha, ok := built[subPrefix]; ok { entries[i].sha = sha continue } newShaStr, err := build(subPrefix) if err != nil { return nil, err } entries[i].sha = newShaStr } } shaStr, err := writeTree(ctx, repoPath, entries) if err != nil { return nil, err } shaBytes, err := hex.DecodeString(shaStr) if err != nil { return nil, err } built[prefix] = shaBytes return shaBytes, nil } rootShaBytes, err := build("") if err != nil { return "", err } return hex.EncodeToString(rootShaBytes), nil } type treeEntry struct { mode string // like "100644" name string // individual name sha []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(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),
Author: bytesToString(authorName), Email: bytesToString(authorEmail), Date: bytesToString(authorDate), Message: bytesToString(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(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(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),
Name: bytesToString(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))
fmt.Fprint(writer, bytesToString(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(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(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),
Name: bytesToString(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))
rendered := renderHighlightedFile(pathSpec, bytesToString(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(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
return "README", template.HTML("<pre>" + html.EscapeString(bytesToString(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())
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("<pre>" + html.EscapeString(string(data)) + "</pre>") //#nosec G203
return filename, template.HTML("<pre>" + html.EscapeString(bytesToString(data)) + "</pre>") //#nosec G203
} }