Lindenii Project Forge
Avoid allocations by using unsafe strinng/bytes conversions
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> // //go:build linux package main import ( "bytes" "context" "encoding/binary" "errors" "fmt" "io" "net" "os" "path/filepath" "strconv" "strings" "syscall" "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/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" ) var ( errGetFD = errors.New("unable to get file descriptor") errGetUcred = errors.New("failed getsockopt") ) // hooksHandler handles a connection from hookc via the // unix socket. func hooksHandler(conn net.Conn) { var ctx context.Context var cancel context.CancelFunc var ucred *syscall.Ucred var err error var cookie []byte var packPass packPass var sshStderr io.Writer var hookRet byte defer conn.Close() ctx, cancel = context.WithCancel(context.Background()) defer cancel() // There aren't reasonable cases where someone would run this as // another user. if ucred, err = getUcred(conn); err != nil { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nUnable to get peer credentials: %v", err) return } uint32uid := uint32(os.Getuid()) //#nosec G115 if ucred.Uid != uint32uid { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nUID mismatch") return } cookie = make([]byte, 64) if _, err = conn.Read(cookie); err != nil { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nFailed to read cookie: %v", err) return } { var ok bool
packPass, ok = packPasses.Load(string(cookie))
packPass, ok = packPasses.Load(bytesToString(cookie))
if !ok { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nInvalid handler cookie") return } } sshStderr = packPass.session.Stderr() _, _ = sshStderr.Write([]byte{'\n'}) hookRet = func() byte { var argc64 uint64 if err = binary.Read(conn, binary.NativeEndian, &argc64); err != nil { writeRedError(sshStderr, "Failed to read argc: %v", err) return 1 } var args []string for range argc64 { var arg bytes.Buffer for { nextByte := make([]byte, 1) n, err := conn.Read(nextByte) if err != nil || n != 1 { writeRedError(sshStderr, "Failed to read arg: %v", err) return 1 } if nextByte[0] == 0 { break } arg.WriteByte(nextByte[0]) } args = append(args, arg.String()) } gitEnv := make(map[string]string) for { var envLine bytes.Buffer for { nextByte := make([]byte, 1) n, err := conn.Read(nextByte) if err != nil || n != 1 { writeRedError(sshStderr, "Failed to read environment variable: %v", err) return 1 } if nextByte[0] == 0 { break } envLine.WriteByte(nextByte[0]) } if envLine.Len() == 0 { break } kv := envLine.String() parts := strings.SplitN(kv, "=", 2) if len(parts) < 2 { writeRedError(sshStderr, "Invalid environment variable line: %v", kv) return 1 } gitEnv[parts[0]] = parts[1] } var stdin bytes.Buffer if _, err = io.Copy(&stdin, conn); err != nil { writeRedError(conn, "Failed to read to the stdin buffer: %v", err) } switch filepath.Base(args[0]) { case "pre-receive": if packPass.directAccess { return 0 } allOK := true for { var line, oldOID, rest, newIOID, refName string var found bool var oldHash, newHash plumbing.Hash var oldCommit, newCommit *object.Commit var pushOptCount int pushOptCount, err = strconv.Atoi(gitEnv["GIT_PUSH_OPTION_COUNT"]) if err != nil { writeRedError(sshStderr, "Failed to parse GIT_PUSH_OPTION_COUNT: %v", err) return 1 } // TODO: Allow existing users (even if they are already federated or registered) to add a federated user ID... though perhaps this should be in the normal SSH interface instead of the git push interface? // Also it'd be nice to be able to combine users or whatever if packPass.contribReq == "federated" && packPass.userType != "federated" && packPass.userType != "registered" { if pushOptCount == 0 { writeRedError(sshStderr, "This repo requires contributors to be either federated or registered users. You must supply your federated user ID as a push option. For example, git push -o fedid=sr.ht:runxiyu") return 1 } for pushOptIndex := range pushOptCount { pushOpt, ok := gitEnv[fmt.Sprintf("GIT_PUSH_OPTION_%d", pushOptIndex)] if !ok { writeRedError(sshStderr, "Failed to get push option %d", pushOptIndex) return 1 } if strings.HasPrefix(pushOpt, "fedid=") { fedUserID := strings.TrimPrefix(pushOpt, "fedid=") service, username, found := strings.Cut(fedUserID, ":") if !found { writeRedError(sshStderr, "Invalid federated user identifier %#v does not contain a colon", fedUserID) return 1 } ok, err := fedauth(ctx, packPass.userID, service, username, packPass.pubkey) if err != nil { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: %v", fedUserID, err) return 1 } if !ok { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: you don't seem to be on the list", fedUserID) return 1 } break } if pushOptIndex == pushOptCount-1 { writeRedError(sshStderr, "This repo requires contributors to be either federated or registered users. You must supply your federated user ID as a push option. For example, git push -o fedid=sr.ht:runxiyu") return 1 } } } line, err = stdin.ReadString('\n') if errors.Is(err, io.EOF) { break } else if err != nil { writeRedError(sshStderr, "Failed to read pre-receive line: %v", err) return 1 } line = line[:len(line)-1] oldOID, rest, found = strings.Cut(line, " ") if !found { writeRedError(sshStderr, "Invalid pre-receive line: %v", line) return 1 } newIOID, refName, found = strings.Cut(rest, " ") if !found { writeRedError(sshStderr, "Invalid pre-receive line: %v", line) return 1 } if strings.HasPrefix(refName, "refs/heads/contrib/") { if allZero(oldOID) { // New branch fmt.Fprintln(sshStderr, ansiec.Blue+"POK"+ansiec.Reset, refName) var newMRID int if packPass.userID != 0 { err = database.QueryRow(ctx, "INSERT INTO merge_requests (repo_id, creator, source_ref, status) VALUES ($1, $2, $3, 'open') RETURNING id", packPass.repoID, packPass.userID, strings.TrimPrefix(refName, "refs/heads/"), ).Scan(&newMRID) } else { err = database.QueryRow(ctx, "INSERT INTO merge_requests (repo_id, source_ref, status) VALUES ($1, $2, 'open') RETURNING id", packPass.repoID, strings.TrimPrefix(refName, "refs/heads/"), ).Scan(&newMRID) } if err != nil { writeRedError(sshStderr, "Error creating merge request: %v", err) return 1 } mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRID) fmt.Fprintln(sshStderr, ansiec.Blue+"Created merge request at", mergeRequestWebURL+ansiec.Reset) select { case ircSendBuffered <- "PRIVMSG #chat :New merge request at " + mergeRequestWebURL: default: clog.Error("IRC SendQ exceeded") } } else { // Existing contrib branch var existingMRUser int var isAncestor bool err = database.QueryRow(ctx, "SELECT COALESCE(creator, 0) FROM merge_requests WHERE source_ref = $1 AND repo_id = $2", strings.TrimPrefix(refName, "refs/heads/"), packPass.repoID, ).Scan(&existingMRUser) if err != nil { if errors.Is(err, pgx.ErrNoRows) { writeRedError(sshStderr, "No existing merge request for existing contrib branch: %v", err) } else { writeRedError(sshStderr, "Error querying for existing merge request: %v", err) } return 1 } if existingMRUser == 0 { allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(branch belongs to unowned MR)") continue } if existingMRUser != packPass.userID { allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(branch belongs another user's MR)") continue } oldHash = plumbing.NewHash(oldOID) if oldCommit, err = packPass.repo.CommitObject(oldHash); err != nil { writeRedError(sshStderr, "Daemon failed to get old commit: %v", err) return 1 } // Potential BUG: I'm not sure if new_commit is guaranteed to be // detectable as they haven't been merged into the main repo's // objects yet. But it seems to work, and I don't think there's // any reason for this to only work intermitently. newHash = plumbing.NewHash(newIOID) if newCommit, err = packPass.repo.CommitObject(newHash); err != nil { writeRedError(sshStderr, "Daemon failed to get new commit: %v", err) return 1 } if isAncestor, err = oldCommit.IsAncestor(newCommit); err != nil { writeRedError(sshStderr, "Daemon failed to check if old commit is ancestor: %v", err) return 1 } if !isAncestor { // TODO: Create MR snapshot ref instead allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(force pushes are not supported yet)") continue } fmt.Fprintln(sshStderr, ansiec.Blue+"POK"+ansiec.Reset, refName) } } else { // Non-contrib branch allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(you cannot push to branches outside of contrib/*)") } } fmt.Fprintln(sshStderr) if allOK { fmt.Fprintln(sshStderr, "Overall "+ansiec.Green+"ACK"+ansiec.Reset+" (all checks passed)") return 0 } fmt.Fprintln(sshStderr, "Overall "+ansiec.Red+"NAK"+ansiec.Reset+" (one or more branches failed checks)") return 1 default: fmt.Fprintln(sshStderr, ansiec.Red+"Invalid hook:", args[0]+ansiec.Reset) return 1 } }() fmt.Fprintln(sshStderr) _, _ = conn.Write([]byte{hookRet}) } func serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() if err != nil { return err } go hooksHandler(conn) } } func getUcred(conn net.Conn) (ucred *syscall.Ucred, err error) { unixConn := conn.(*net.UnixConn) var unixConnFD *os.File if unixConnFD, err = unixConn.File(); err != nil { return nil, errGetFD } defer unixConnFD.Close() if ucred, err = syscall.GetsockoptUcred(int(unixConnFD.Fd()), syscall.SOL_SOCKET, syscall.SO_PEERCRED); err != nil { return nil, errGetUcred } return ucred, nil } func allZero(s string) bool { for _, r := range s { if r != '0' { return false } } return true }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> // //go:build !linux package main import ( "bytes" "context" "encoding/binary" "errors" "fmt" "io" "net" "path/filepath" "strconv" "strings" "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/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" ) var errGetFD = errors.New("unable to get file descriptor") // hooksHandler handles a connection from hookc via the // unix socket. func hooksHandler(conn net.Conn) { var ctx context.Context var cancel context.CancelFunc var err error var cookie []byte var packPass packPass var sshStderr io.Writer var hookRet byte defer conn.Close() ctx, cancel = context.WithCancel(context.Background()) defer cancel() // TODO: Validate that the connection is from the right user. cookie = make([]byte, 64) if _, err = conn.Read(cookie); err != nil { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nFailed to read cookie: %v", err) return } { var ok bool
packPass, ok = packPasses.Load(string(cookie))
packPass, ok = packPasses.Load(bytesToString(cookie))
if !ok { if _, err = conn.Write([]byte{1}); err != nil { return } writeRedError(conn, "\nInvalid handler cookie") return } } sshStderr = packPass.session.Stderr() _, _ = sshStderr.Write([]byte{'\n'}) hookRet = func() byte { var argc64 uint64 if err = binary.Read(conn, binary.NativeEndian, &argc64); err != nil { writeRedError(sshStderr, "Failed to read argc: %v", err) return 1 } var args []string for range argc64 { var arg bytes.Buffer for { nextByte := make([]byte, 1) n, err := conn.Read(nextByte) if err != nil || n != 1 { writeRedError(sshStderr, "Failed to read arg: %v", err) return 1 } if nextByte[0] == 0 { break } arg.WriteByte(nextByte[0]) } args = append(args, arg.String()) } gitEnv := make(map[string]string) for { var envLine bytes.Buffer for { nextByte := make([]byte, 1) n, err := conn.Read(nextByte) if err != nil || n != 1 { writeRedError(sshStderr, "Failed to read environment variable: %v", err) return 1 } if nextByte[0] == 0 { break } envLine.WriteByte(nextByte[0]) } if envLine.Len() == 0 { break } kv := envLine.String() parts := strings.SplitN(kv, "=", 2) if len(parts) < 2 { writeRedError(sshStderr, "Invalid environment variable line: %v", kv) return 1 } gitEnv[parts[0]] = parts[1] } var stdin bytes.Buffer if _, err = io.Copy(&stdin, conn); err != nil { writeRedError(conn, "Failed to read to the stdin buffer: %v", err) } switch filepath.Base(args[0]) { case "pre-receive": if packPass.directAccess { return 0 } allOK := true for { var line, oldOID, rest, newIOID, refName string var found bool var oldHash, newHash plumbing.Hash var oldCommit, newCommit *object.Commit var pushOptCount int pushOptCount, err = strconv.Atoi(gitEnv["GIT_PUSH_OPTION_COUNT"]) if err != nil { writeRedError(sshStderr, "Failed to parse GIT_PUSH_OPTION_COUNT: %v", err) return 1 } // TODO: Allow existing users (even if they are already federated or registered) to add a federated user ID... though perhaps this should be in the normal SSH interface instead of the git push interface? // Also it'd be nice to be able to combine users or whatever if packPass.contribReq == "federated" && packPass.userType != "federated" && packPass.userType != "registered" { if pushOptCount == 0 { writeRedError(sshStderr, "This repo requires contributors to be either federated or registered users. You must supply your federated user ID as a push option. For example, git push -o fedid=sr.ht:runxiyu") return 1 } for pushOptIndex := range pushOptCount { pushOpt, ok := gitEnv[fmt.Sprintf("GIT_PUSH_OPTION_%d", pushOptIndex)] if !ok { writeRedError(sshStderr, "Failed to get push option %d", pushOptIndex) return 1 } if strings.HasPrefix(pushOpt, "fedid=") { fedUserID := strings.TrimPrefix(pushOpt, "fedid=") service, username, found := strings.Cut(fedUserID, ":") if !found { writeRedError(sshStderr, "Invalid federated user identifier %#v does not contain a colon", fedUserID) return 1 } ok, err := fedauth(ctx, packPass.userID, service, username, packPass.pubkey) if err != nil { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: %v", fedUserID, err) return 1 } if !ok { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: you don't seem to be on the list", fedUserID) return 1 } break } if pushOptIndex == pushOptCount-1 { writeRedError(sshStderr, "This repo requires contributors to be either federated or registered users. You must supply your federated user ID as a push option. For example, git push -o fedid=sr.ht:runxiyu") return 1 } } } line, err = stdin.ReadString('\n') if errors.Is(err, io.EOF) { break } else if err != nil { writeRedError(sshStderr, "Failed to read pre-receive line: %v", err) return 1 } line = line[:len(line)-1] oldOID, rest, found = strings.Cut(line, " ") if !found { writeRedError(sshStderr, "Invalid pre-receive line: %v", line) return 1 } newIOID, refName, found = strings.Cut(rest, " ") if !found { writeRedError(sshStderr, "Invalid pre-receive line: %v", line) return 1 } if strings.HasPrefix(refName, "refs/heads/contrib/") { if allZero(oldOID) { // New branch fmt.Fprintln(sshStderr, ansiec.Blue+"POK"+ansiec.Reset, refName) var newMRID int if packPass.userID != 0 { err = database.QueryRow(ctx, "INSERT INTO merge_requests (repo_id, creator, source_ref, status) VALUES ($1, $2, $3, 'open') RETURNING id", packPass.repoID, packPass.userID, strings.TrimPrefix(refName, "refs/heads/"), ).Scan(&newMRID) } else { err = database.QueryRow(ctx, "INSERT INTO merge_requests (repo_id, source_ref, status) VALUES ($1, $2, 'open') RETURNING id", packPass.repoID, strings.TrimPrefix(refName, "refs/heads/"), ).Scan(&newMRID) } if err != nil { writeRedError(sshStderr, "Error creating merge request: %v", err) return 1 } mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRID) fmt.Fprintln(sshStderr, ansiec.Blue+"Created merge request at", mergeRequestWebURL+ansiec.Reset) select { case ircSendBuffered <- "PRIVMSG #chat :New merge request at " + mergeRequestWebURL: default: clog.Error("IRC SendQ exceeded") } } else { // Existing contrib branch var existingMRUser int var isAncestor bool err = database.QueryRow(ctx, "SELECT COALESCE(creator, 0) FROM merge_requests WHERE source_ref = $1 AND repo_id = $2", strings.TrimPrefix(refName, "refs/heads/"), packPass.repoID, ).Scan(&existingMRUser) if err != nil { if errors.Is(err, pgx.ErrNoRows) { writeRedError(sshStderr, "No existing merge request for existing contrib branch: %v", err) } else { writeRedError(sshStderr, "Error querying for existing merge request: %v", err) } return 1 } if existingMRUser == 0 { allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(branch belongs to unowned MR)") continue } if existingMRUser != packPass.userID { allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(branch belongs another user's MR)") continue } oldHash = plumbing.NewHash(oldOID) if oldCommit, err = packPass.repo.CommitObject(oldHash); err != nil { writeRedError(sshStderr, "Daemon failed to get old commit: %v", err) return 1 } // Potential BUG: I'm not sure if new_commit is guaranteed to be // detectable as they haven't been merged into the main repo's // objects yet. But it seems to work, and I don't think there's // any reason for this to only work intermitently. newHash = plumbing.NewHash(newIOID) if newCommit, err = packPass.repo.CommitObject(newHash); err != nil { writeRedError(sshStderr, "Daemon failed to get new commit: %v", err) return 1 } if isAncestor, err = oldCommit.IsAncestor(newCommit); err != nil { writeRedError(sshStderr, "Daemon failed to check if old commit is ancestor: %v", err) return 1 } if !isAncestor { // TODO: Create MR snapshot ref instead allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(force pushes are not supported yet)") continue } fmt.Fprintln(sshStderr, ansiec.Blue+"POK"+ansiec.Reset, refName) } } else { // Non-contrib branch allOK = false fmt.Fprintln(sshStderr, ansiec.Red+"NAK"+ansiec.Reset, refName, "(you cannot push to branches outside of contrib/*)") } } fmt.Fprintln(sshStderr) if allOK { fmt.Fprintln(sshStderr, "Overall "+ansiec.Green+"ACK"+ansiec.Reset+" (all checks passed)") return 0 } fmt.Fprintln(sshStderr, "Overall "+ansiec.Red+"NAK"+ansiec.Reset+" (one or more branches failed checks)") return 1 default: fmt.Fprintln(sshStderr, ansiec.Red+"Invalid hook:", args[0]+ansiec.Reset) return 1 } }() fmt.Fprintln(sshStderr) _, _ = conn.Write([]byte{hookRet}) } func serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() if err != nil { return err } go hooksHandler(conn) } } func allZero(s string) bool { for _, r := range s { if r != '0' { return false } } return true }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "fmt" "net/http" "strings" "time" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) func httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) { var rawPathSpec, pathSpec string var repo *git.Repository var refHash plumbing.Hash var refHashSlice []byte var commitObj *object.Commit var tree *object.Tree var err error rawPathSpec = params["rest"].(string) repo, pathSpec = params["repo"].(*git.Repository), strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec if refHash, err = getRefHash(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil { errorPage500(writer, params, "Error getting ref hash: "+err.Error()) return } refHashSlice = refHash[:]
cacheHandle := append(refHashSlice, []byte(pathSpec)...) //nolint:gocritic
cacheHandle := append(refHashSlice, stringToBytes(pathSpec)...) //nolint:gocritic
if value, found := treeReadmeCache.Get(cacheHandle); found { params["files"] = value.DisplayTree renderTemplate(writer, "repo_raw_dir", params) return } if value, found := commitPathFileRawCache.Get(cacheHandle); found { fmt.Fprint(writer, value) return } if commitObj, err = repo.CommitObject(refHash); err != nil { errorPage500(writer, params, "Error getting commit object: "+err.Error()) return } if tree, err = commitObj.Tree(); err != nil { errorPage500(writer, params, "Error getting file tree: "+err.Error()) return } start := time.Now() var target *object.Tree if pathSpec == "" { target = tree } else { if target, err = tree.Tree(pathSpec); err != nil { var file *object.File var fileContent string if file, err = tree.File(pathSpec); err != nil { errorPage500(writer, params, "Error retrieving path: "+err.Error()) return } if redirectNoDir(writer, request) { return } if fileContent, err = file.Contents(); err != nil { errorPage500(writer, params, "Error reading file: "+err.Error()) return } cost := time.Since(start).Nanoseconds() commitPathFileRawCache.Set(cacheHandle, fileContent, cost) fmt.Fprint(writer, fileContent) return } } if redirectDir(writer, request) { return } displayTree := makeDisplayTree(target) readmeFilename, readmeRendered := renderReadmeAtTree(target) cost := time.Since(start).Nanoseconds() params["files"] = displayTree params["readme_filename"] = readmeFilename params["readme"] = readmeRendered treeReadmeCache.Set(cacheHandle, treeReadmeCacheEntry{ DisplayTree: displayTree, ReadmeFilename: readmeFilename, ReadmeRendered: readmeRendered, }, cost) renderTemplate(writer, "repo_raw_dir", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "bytes" "html/template" "net/http" "path" "strings" "time" "github.com/alecthomas/chroma/v2" chromaHTML "github.com/alecthomas/chroma/v2/formatters/html" chromaLexers "github.com/alecthomas/chroma/v2/lexers" chromaStyles "github.com/alecthomas/chroma/v2/styles" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) func httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, params map[string]any) { var rawPathSpec, pathSpec string var repo *git.Repository var refHash plumbing.Hash var refHashSlice []byte var commitObject *object.Commit var tree *object.Tree var err error rawPathSpec = params["rest"].(string) repo, pathSpec = params["repo"].(*git.Repository), strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec if refHash, err = getRefHash(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil { errorPage500(writer, params, "Error getting ref hash: "+err.Error()) return } refHashSlice = refHash[:]
cacheHandle := append(refHashSlice, []byte(pathSpec)...) //nolint:gocritic
cacheHandle := append(refHashSlice, stringToBytes(pathSpec)...) //nolint:gocritic
if value, found := treeReadmeCache.Get(cacheHandle); found { params["files"] = value.DisplayTree params["readme_filename"] = value.ReadmeFilename params["readme"] = value.ReadmeRendered renderTemplate(writer, "repo_tree_dir", params) return } if value, found := commitPathFileHTMLCache.Get(cacheHandle); found { params["file_contents"] = value renderTemplate(writer, "repo_tree_file", params) return } start := time.Now() var target *object.Tree if pathSpec == "" { if commitObject, err = repo.CommitObject(refHash); err != nil { errorPage500(writer, params, "Error getting commit object: "+err.Error()) return } if tree, err = commitObject.Tree(); err != nil { errorPage500(writer, params, "Error getting file tree: "+err.Error()) return } displayTree := makeDisplayTree(tree) readmeFilename, readmeRendered := renderReadmeAtTree(tree) cost := time.Since(start).Nanoseconds() params["files"] = displayTree params["readme_filename"] = readmeFilename params["readme"] = readmeRendered entry := treeReadmeCacheEntry{ DisplayTree: displayTree, ReadmeFilename: readmeFilename, ReadmeRendered: readmeRendered, } treeReadmeCache.Set(cacheHandle, entry, cost) renderTemplate(writer, "repo_tree_dir", params) return } if commitObject, err = repo.CommitObject(refHash); err != nil { errorPage500(writer, params, "Error getting commit object: "+err.Error()) return } if tree, err = commitObject.Tree(); err != nil { errorPage500(writer, params, "Error getting file tree: "+err.Error()) return } if target, err = tree.Tree(pathSpec); err != nil { var file *object.File var fileContent string var lexer chroma.Lexer var iterator chroma.Iterator var style *chroma.Style var formatter *chromaHTML.Formatter var formattedHTML template.HTML if file, err = tree.File(pathSpec); err != nil { errorPage500(writer, params, "Error retrieving path: "+err.Error()) return } if redirectNoDir(writer, request) { return } if fileContent, err = file.Contents(); err != nil { errorPage500(writer, params, "Error reading file: "+err.Error()) return } lexer = chromaLexers.Match(pathSpec) if lexer == nil { lexer = chromaLexers.Fallback } if iterator, err = lexer.Tokenise(nil, fileContent); err != nil { errorPage500(writer, params, "Error tokenizing code: "+err.Error()) return } var formattedHTMLStr bytes.Buffer style = chromaStyles.Get("autumn") formatter = chromaHTML.New(chromaHTML.WithClasses(true), chromaHTML.TabWidth(8)) if err = formatter.Format(&formattedHTMLStr, style, iterator); err != nil { errorPage500(writer, params, "Error formatting code: "+err.Error()) return } formattedHTML = template.HTML(formattedHTMLStr.Bytes()) //#nosec G203 cost := time.Since(start).Nanoseconds() commitPathFileHTMLCache.Set(cacheHandle, formattedHTML, cost) params["file_contents"] = formattedHTML renderTemplate(writer, "repo_tree_file", params) return } if len(rawPathSpec) != 0 && rawPathSpec[len(rawPathSpec)-1] != '/' { http.Redirect(writer, request, path.Base(pathSpec)+"/", http.StatusSeeOther) return } displayTree := makeDisplayTree(target) readmeFilename, readmeRendered := renderReadmeAtTree(target) cost := time.Since(start).Nanoseconds() entry := treeReadmeCacheEntry{ DisplayTree: displayTree, ReadmeFilename: readmeFilename, ReadmeRendered: readmeRendered, } treeReadmeCache.Set(cacheHandle, entry, cost) params["readme_filename"], params["readme"] = readmeFilename, readmeRendered params["files"] = displayTree renderTemplate(writer, "repo_tree_dir", params) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: 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)) func renderReadmeAtTree(tree *object.Tree) (readmeFilename string, readmeRenderedSafeHTML template.HTML) { var readmeRenderedUnsafe bytes.Buffer var readmeFile *object.File var readmeFileContents string var err error if readmeFile, err = tree.File("README"); err == nil { if readmeFileContents, err = readmeFile.Contents(); err != nil { return "Error fetching README", escapeHTML("Unable to fetch contents of README: " + err.Error()) } return "README", template.HTML("<pre>" + html.EscapeString(readmeFileContents) + "</pre>") //#nosec G203 } if readmeFile, err = tree.File("README.md"); err == nil { if readmeFileContents, err = readmeFile.Contents(); err != nil { return "Error fetching README", escapeHTML("Unable to fetch contents of README: " + err.Error()) }
if err = markdownConverter.Convert([]byte(readmeFileContents), &readmeRenderedUnsafe); err != nil {
if err = markdownConverter.Convert(stringToBytes(readmeFileContents), &readmeRenderedUnsafe); err != nil {
return "Error fetching README", escapeHTML("Unable to render README: " + err.Error()) } return "README.md", template.HTML(bluemonday.UGCPolicy().SanitizeBytes(readmeRenderedUnsafe.Bytes())) //#nosec G203 } if readmeFile, err = tree.File("README.org"); err == nil { if readmeFileContents, err = readmeFile.Contents(); err != nil { return "Error fetching README", escapeHTML("Unable to fetch contents of README: " + err.Error()) } orgHTML, err := org.New().Parse(strings.NewReader(readmeFileContents), readmeFilename).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(orgHTML)) //#nosec G203 } return "", "" } func escapeHTML(s string) template.HTML { return template.HTML(html.EscapeString(s)) //#nosec G203 }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "embed" "html/template" "io/fs" "net/http" "github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2/html" ) // We embed all source for easy AGPL compliance. // //go:embed .gitignore .gitattributes //go:embed LICENSE README.md //go:embed *.go go.mod go.sum //go:embed *.scfg //go:embed Makefile //go:embed static/* templates/* scripts/* sql/* //go:embed hookc/*.c //go:embed vendor/* var sourceFS embed.FS var sourceHandler = http.StripPrefix( "/:/source/", http.FileServer(http.FS(sourceFS)), ) //go:embed templates/* static/* hookc/hookc var resourcesFS embed.FS var templates *template.Template func loadTemplates() (err error) { minifier := minify.New() minifierOptions := html.Minifier{ TemplateDelims: [2]string{"{{", "}}"}, KeepDefaultAttrVals: true, } //exhaustruct:ignore minifier.Add("text/html", &minifierOptions) templates = template.New("templates").Funcs(template.FuncMap{ "first_line": firstLine, "base_name": baseName, "path_escape": pathEscape, "query_escape": queryEscape, "dereference_error": dereferenceOrZero[error], "minus": minus, }) err = fs.WalkDir(resourcesFS, "templates", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { content, err := fs.ReadFile(resourcesFS, path) if err != nil { return err } minified, err := minifier.Bytes("text/html", content) if err != nil { return err }
_, err = templates.Parse(string(minified))
_, err = templates.Parse(bytesToString(minified))
if err != nil { return err } } return nil }) return err } var staticHandler http.Handler func init() { staticFS, err := fs.Sub(resourcesFS, "static") if err != nil { panic(err) } staticHandler = http.StripPrefix("/:/static/", http.FileServer(http.FS(staticFS))) }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu <https://runxiyu.org> package main import ( "fmt" "net" "os" "strings" gliderSSH "github.com/gliderlabs/ssh" "go.lindenii.runxiyu.org/lindenii-common/ansiec" "go.lindenii.runxiyu.org/lindenii-common/clog" goSSH "golang.org/x/crypto/ssh" ) var ( serverPubkeyString string serverPubkeyFP string serverPubkey goSSH.PublicKey ) func serveSSH(listener net.Listener) error { var hostKeyBytes []byte var hostKey goSSH.Signer var err error var server *gliderSSH.Server if hostKeyBytes, err = os.ReadFile(config.SSH.Key); err != nil { return err } if hostKey, err = goSSH.ParsePrivateKey(hostKeyBytes); err != nil { return err } serverPubkey = hostKey.PublicKey()
serverPubkeyString = string(goSSH.MarshalAuthorizedKey(serverPubkey))
serverPubkeyString = bytesToString(goSSH.MarshalAuthorizedKey(serverPubkey))
serverPubkeyFP = goSSH.FingerprintSHA256(serverPubkey) server = &gliderSSH.Server{ Handler: func(session gliderSSH.Session) { clientPubkey := session.PublicKey() var clientPubkeyStr string if clientPubkey != nil {
clientPubkeyStr = strings.TrimSuffix(string(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n")
clientPubkeyStr = strings.TrimSuffix(bytesToString(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n")
} clog.Info("Incoming SSH: " + session.RemoteAddr().String() + " " + clientPubkeyStr + " " + session.RawCommand()) fmt.Fprintln(session.Stderr(), ansiec.Blue+"Lindenii Forge "+VERSION+", source at "+strings.TrimSuffix(config.HTTP.Root, "/")+"/:/source/"+ansiec.Reset+"\r") cmd := session.Command() if len(cmd) < 2 { fmt.Fprintln(session.Stderr(), "Insufficient arguments\r") return } switch cmd[0] { case "git-upload-pack": if len(cmd) > 2 { fmt.Fprintln(session.Stderr(), "Too many arguments\r") return } err = sshHandleUploadPack(session, clientPubkeyStr, cmd[1]) case "git-receive-pack": if len(cmd) > 2 { fmt.Fprintln(session.Stderr(), "Too many arguments\r") return } err = sshHandleRecvPack(session, clientPubkeyStr, cmd[1]) default: fmt.Fprintln(session.Stderr(), "Unsupported command: "+cmd[0]+"\r") return } if err != nil { fmt.Fprintln(session.Stderr(), err.Error()) return } }, PublicKeyHandler: func(_ gliderSSH.Context, _ gliderSSH.PublicKey) bool { return true }, KeyboardInteractiveHandler: func(_ gliderSSH.Context, _ goSSH.KeyboardInteractiveChallenge) bool { return true }, // It is intentional that we do not check any credentials and accept all connections. // This allows all users to connect and clone repositories. However, the public key // is passed to handlers, so e.g. the push handler could check the key and reject the // push if it needs to. } //exhaustruct:ignore server.AddHostKey(hostKey) if err = server.Serve(listener); err != nil { clog.Fatal(1, "Serving SSH: "+err.Error()) } return nil }
// SPDX-License-Identifier: BSD-2-Clause // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> 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(&b[0], len(b)) }