From 655b6b211ae6df0186abd740f248939f7ddeaec1 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Mon, 31 Mar 2025 16:59:18 +0800 Subject: [PATCH] Add descriptive comments to most Go functions --- acl.go | 6 ++++-- cache_commit_path_file_html.go | 1 + cache_commit_path_file_raw.go | 1 + cache_commit_tree_readme.go | 1 + cache_index_commits_display.go | 1 + config.go | 18 +++++++++++++----- database.go | 15 ++++++++++++++- fedauth.go | 4 ++++ git_format_patch.go | 4 ++-- git_hooks_deploy.go | 6 +++--- git_hooks_handle_linux.go | 9 +++++++++ git_hooks_handle_other.go | 7 +++++++ git_init.go | 4 ++-- git_misc.go | 39 +++++++++++++++++++++++---------------- http_auth.go | 2 ++ http_global.go | 4 +++- http_handle_branches.go | 1 + http_handle_gc.go | 5 +++++ http_handle_group_index.go | 3 +++ http_handle_index.go | 2 ++ http_handle_login.go | 1 + http_handle_repo_commit.go | 8 +++++--- http_handle_repo_contrib_index.go | 3 +++ http_handle_repo_contrib_one.go | 2 ++ http_handle_repo_index.go | 1 + http_handle_repo_info.go | 4 ++++ http_handle_repo_log.go | 5 ++++- http_handle_repo_raw.go | 2 ++ http_handle_repo_tree.go | 4 ++++ http_handle_repo_upload_pack.go | 2 ++ http_handle_users.go | 1 + http_server.go | 4 ++++ http_template_funcs.go | 15 ++++++++++----- irc.go | 3 +++ iter.go | 23 +++++++++++++++++++++++ readme_to_html.go | 3 +++ remote_url.go | 4 ++++ resources.go | 4 +++- ssh_handle_receive_pack.go | 3 +++ ssh_server.go | 3 +++ ssh_utils.go | 5 +++++ url.go | 15 +++++++++++++++ users.go | 3 +++ utils.go | 2 ++ diff --git a/acl.go b/acl.go index 0595d55c9fa4b434ff31c897b6a2a73036b84300..5f242d57b9099e1f617b856b726cdb8286209d83 100644 --- a/acl.go +++ b/acl.go @@ -9,8 +9,10 @@ "github.com/jackc/pgx/v5/pgtype" ) -// getRepoInfo returns the filesystem path and direct -// access permission for a given repo and a provided ssh public key. +// getRepoInfo returns the filesystem path and direct access permission for a +// given repo and a provided ssh public key. +// +// TODO: Revamp. func getRepoInfo(ctx context.Context, groupPath []string, repoName, sshPubkey string) (repoID int, fsPath string, access bool, contribReq, userType string, userID int, err error) { err = database.QueryRow(ctx, ` WITH RECURSIVE group_path_cte AS ( diff --git a/cache_commit_path_file_html.go b/cache_commit_path_file_html.go index 944a6e77b51da2c59b20b7e55d1d29435b39b30e..9254d6b8ba85c26331ca9e4147f524b66029dafd 100644 --- a/cache_commit_path_file_html.go +++ b/cache_commit_path_file_html.go @@ -10,6 +10,7 @@ "github.com/dgraph-io/ristretto/v2" "go.lindenii.runxiyu.org/lindenii-common/clog" ) +// The key is the commit ID raw hash, followed by the file path. var commitPathFileHTMLCache *ristretto.Cache[[]byte, template.HTML] func init() { diff --git a/cache_commit_path_file_raw.go b/cache_commit_path_file_raw.go index bd7cc7e05eff28ab6090f54125ce64a1a5324bae..89b817b6fee67f0291d33e131a214a9c80b46edc 100644 --- a/cache_commit_path_file_raw.go +++ b/cache_commit_path_file_raw.go @@ -8,6 +8,7 @@ "github.com/dgraph-io/ristretto/v2" "go.lindenii.runxiyu.org/lindenii-common/clog" ) +// The key is the commit ID raw hash, followed by the file path. var commitPathFileRawCache *ristretto.Cache[[]byte, string] func init() { diff --git a/cache_commit_tree_readme.go b/cache_commit_tree_readme.go index de58d71a9a3356683486326a389e9f887a5a77ad..afd7f3e39c6d27bff64e4bf4d64ae0de5a8ddf47 100644 --- a/cache_commit_tree_readme.go +++ b/cache_commit_tree_readme.go @@ -16,6 +16,7 @@ ReadmeFilename string ReadmeRendered template.HTML } +// The key is the commit ID raw hash, optionally followed by a path. var treeReadmeCache *ristretto.Cache[[]byte, treeReadmeCacheEntry] func init() { diff --git a/cache_index_commits_display.go b/cache_index_commits_display.go index 786c3d3581b4275b370d48b5b63b20c83efb1aeb..fc28cf11441c4002e990fcdd3a4f7a90d5cd4c01 100644 --- a/cache_index_commits_display.go +++ b/cache_index_commits_display.go @@ -8,6 +8,7 @@ "github.com/dgraph-io/ristretto/v2" "go.lindenii.runxiyu.org/lindenii-common/clog" ) +// The key is the commit ID raw hash. var indexCommitsDisplayCache *ristretto.Cache[[]byte, []commitDisplay] func init() { diff --git a/config.go b/config.go index 8bf05d92f7c4c06d744f8fd69dbf29d2c65d6857..1973f3d5b562944e3d22cf2306d73e1b06f8ce85 100644 --- a/config.go +++ b/config.go @@ -13,8 +13,9 @@ "github.com/jackc/pgx/v5/pgxpool" "go.lindenii.runxiyu.org/lindenii-common/scfg" ) -var database *pgxpool.Pool - +// config holds the global configuration used by this instance. There is +// currently no synchronization mechanism, so it must not be modified after +// request handlers are spawned. var config struct { HTTP struct { Net string `scfg:"net"` @@ -57,16 +58,23 @@ Conn string `scfg:"conn"` } `scfg:"db"` } +// loadConfig loads a configuration file from the specified path and unmarshals +// it to the global [config] struct. This may race with concurrent reads from +// [config]; additional synchronization is necessary if the configuration is to +// be made reloadable. +// +// TODO: Currently, it returns an error when the user specifies any unknown +// configuration patterns, but silently ignores fields in the [config] struct +// that is not present in the user's configuration file. We would prefer the +// exact opposite behavior. func loadConfig(path string) (err error) { var configFile *os.File - var decoder *scfg.Decoder - if configFile, err = os.Open(path); err != nil { return err } defer configFile.Close() - decoder = scfg.NewDecoder(bufio.NewReader(configFile)) + decoder := scfg.NewDecoder(bufio.NewReader(configFile)) if err = decoder.Decode(&config); err != nil { return err } diff --git a/database.go b/database.go index 5205214f30040768a6679014cf5d96e90af16e35..87fa9f492725493eb29527b4fd53d252e6795f1e 100644 --- a/database.go +++ b/database.go @@ -7,10 +7,23 @@ import ( "context" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" ) +// TODO: All database handling logic in all request handlers must be revamped. +// We must ensure that each request has all logic in one transaction (subject +// to exceptions if appropriate) so they get a consistent view of the database +// at a single point. A failure to do so may cause things as serious as +// privilege escalation. + +// database serves as the primary database handle for this entire application. +// Transactions or single reads may be used therefrom. A [pgxpool.Pool] is +// necessary to safely use pgx concurrently; pgx.Conn, etc. are insufficient. +var database *pgxpool.Pool + // queryNameDesc is a helper function that executes a query and returns a -// list of name_desc_t results. +// list of nameDesc results. The query must return two string arguments, i.e. a +// name and a description. func queryNameDesc(ctx context.Context, query string, args ...any) (result []nameDesc, err error) { var rows pgx.Rows diff --git a/fedauth.go b/fedauth.go index ef1b5ecc999cb55d6ffe4b58a241b832bde77507..808fba505060535f675f4dd378b70a0e8532639c 100644 --- a/fedauth.go +++ b/fedauth.go @@ -15,6 +15,8 @@ "github.com/jackc/pgx/v5" ) +// fedauth checks whether a user's SSH public key matches the remote username +// they claim to have on the service. If so, the association is recorded. func fedauth(ctx context.Context, userID int, service, remoteUsername, pubkey string) (bool, error) { var err error @@ -23,6 +25,8 @@ usernameEscaped := url.PathEscape(remoteUsername) var req *http.Request switch service { + // TODO: Services should be configurable by the instance administrator + // and should not be hardcoded in the source code. case "sr.ht": req, err = http.NewRequestWithContext(ctx, http.MethodGet, "https://meta.sr.ht/~"+usernameEscaped+".keys", nil) case "github": diff --git a/git_format_patch.go b/git_format_patch.go index 5b5e04e89b1bf22f1b89ffadde6afa13a1cb7920..79a7474210b3468af37ffc8c1aa41e4a97eab74a 100644 --- a/git_format_patch.go +++ b/git_format_patch.go @@ -12,7 +12,7 @@ "github.com/go-git/go-git/v5/plumbing/object" ) -// get_patch_from_commit formats a commit object as if it was returned by +// fmtCommitPatch formats a commit object as if it was returned by // git-format-patch. func fmtCommitPatch(commit *object.Commit) (final string, err error) { var patch *object.Patch @@ -21,7 +21,7 @@ var author object.Signature var date string var commitTitle, commitDetails string - if _, patch, err = fmtCommitAsPatch(commit); err != nil { + if _, patch, err = commitToPatch(commit); err != nil { return "", err } diff --git a/git_hooks_deploy.go b/git_hooks_deploy.go index eeb043e0dc6d885eb96f107f64177b3d4137fcd1..c9039fecdb0aa83769105098af8fb855f31eb3cb 100644 --- a/git_hooks_deploy.go +++ b/git_hooks_deploy.go @@ -11,9 +11,9 @@ "os" "path/filepath" ) -// deployHooks deploys the git hooks client to the filesystem. -// The git hooks client is expected to be embedded in resources_fs and must be -// pre-compiled during the build process; see the Makefile. +// deployHooks deploys the git hooks client to the filesystem. The git hooks +// client is expected to be embedded in resourcesFS and must be pre-compiled +// during the build process; see the Makefile. func deployHooks() (err error) { err = func() (err error) { var srcFD fs.File diff --git a/git_hooks_handle_linux.go b/git_hooks_handle_linux.go index 812a429bac120fcf588bcec50894b91b20afa796..e316bb7ab6fb6e42f03e97084f425adee3704465 100644 --- a/git_hooks_handle_linux.go +++ b/git_hooks_handle_linux.go @@ -337,6 +337,10 @@ _, _ = conn.Write([]byte{hookRet}) } +// serveGitHooks handles connections on the specified network listener and +// treats incoming connections as those from git hook handlers by spawning +// sessions. The listener must be a SOCK_STREAM UNIX domain socket. The +// function itself blocks. func serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() @@ -347,6 +351,8 @@ go hooksHandler(conn) } } +// getUcred fetches connection credentials as a [syscall.Ucred] from a given +// [net.Conn]. It panics when conn is not a [net.UnixConn]. func getUcred(conn net.Conn) (ucred *syscall.Ucred, err error) { unixConn := conn.(*net.UnixConn) var unixConnFD *os.File @@ -362,6 +368,9 @@ } return ucred, nil } +// allZero returns true if all runes in a given string are '0'. The comparison +// is not constant time and must not be used in contexts where time-based side +// channel attacks are a concern. func allZero(s string) bool { for _, r := range s { if r != '0' { diff --git a/git_hooks_handle_other.go b/git_hooks_handle_other.go index f1b2e04808fa86e31e4bd74ebb973e739ef23691..428578492ea6bb72074cfd8f91a2230d1037bd53 100644 --- a/git_hooks_handle_other.go +++ b/git_hooks_handle_other.go @@ -315,6 +315,10 @@ _, _ = conn.Write([]byte{hookRet}) } +// serveGitHooks handles connections on the specified network listener and +// treats incoming connections as those from git hook handlers by spawning +// sessions. The listener must be a SOCK_STREAM UNIX domain socket. The +// function itself blocks. func serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() @@ -325,6 +329,9 @@ go hooksHandler(conn) } } +// allZero returns true if all runes in a given string are '0'. The comparison +// is not constant time and must not be used in contexts where time-based side +// channel attacks are a concern. func allZero(s string) bool { for _, r := range s { if r != '0' { diff --git a/git_init.go b/git_init.go index 3ea4f5835d9a0a6ba2dfa671e2c24364c31a8545..f1a283ef330b34bf1e87d4518da7cb408525c2b3 100644 --- a/git_init.go +++ b/git_init.go @@ -9,8 +9,8 @@ gitConfig "github.com/go-git/go-git/v5/config" gitFmtConfig "github.com/go-git/go-git/v5/plumbing/format/config" ) -// gitInit initializes a bare git repository with the -// forge-deployed hooks directory as the hooksPath. +// gitInit initializes a bare git repository with the forge-deployed hooks +// directory as the hooksPath. func gitInit(repoPath string) (err error) { var repo *git.Repository var gitConf *gitConfig.Config diff --git a/git_misc.go b/git_misc.go index 7a5e7e25b60abaf825f9753d92d9f8e1f75b699f..a8d7c30e7205d53808502a817a9ded4de3aef340 100644 --- a/git_misc.go +++ b/git_misc.go @@ -18,6 +18,10 @@ "github.com/jackc/pgx/v5/pgtype" ) // openRepo opens a git repository by group and repo name. +// +// TODO: This should be deprecated in favor of doing it in the relevant +// request/router context in the future, as it cannot cover the nuance of +// fields needed. func openRepo(ctx context.Context, groupPath []string, repoName string) (repo *git.Repository, description string, repoID int, err error) { var fsPath string @@ -64,6 +68,7 @@ return } // go-git's tree entries are not friendly for use in HTML templates. +// This struct is a wrapper that is friendlier for use in templating. type displayTreeEntry struct { Name string Mode string @@ -72,6 +77,8 @@ IsFile bool IsSubtree bool } +// makeDisplayTree takes git trees of form [object.Tree] and creates a slice of +// [displayTreeEntry] for easier templating. func makeDisplayTree(tree *object.Tree) (displayTree []displayTreeEntry) { for _, entry := range tree.Entries { displayEntry := displayTreeEntry{} //exhaustruct:ignore @@ -97,6 +104,11 @@ } return displayTree } +// commitIterSeqErr creates an [iter.Seq[*object.Commit]] from an +// [object.CommitIter], and additionally returns a pointer to error. +// The pointer to error is guaranteed to be populated with either nil or the +// error returned by the commit iterator after the returned iterator is +// finished. func commitIterSeqErr(commitIter object.CommitIter) (iter.Seq[*object.Commit], *error) { var err error return func(yield func(*object.Commit) bool) { @@ -116,21 +128,8 @@ } }, &err } -func iterSeqLimit[T any](s iter.Seq[T], n uint) iter.Seq[T] { - return func(yield func(T) bool) { - var iterations uint - for v := range s { - if iterations > n-1 { - return - } - if !yield(v) { - return - } - iterations++ - } - } -} - +// getRecentCommits fetches numCommits commits, starting from the headHash in a +// repo. func getRecentCommits(repo *git.Repository, headHash plumbing.Hash, numCommits int) (recentCommits []*object.Commit, err error) { var commitIter object.CommitIter var thisCommit *object.Commit @@ -165,6 +164,9 @@ } return recentCommits, err } +// getRecentCommitsDisplay generates a slice of [commitDisplay] friendly for +// use in HTML templates, consisting of numCommits commits from headhash in the +// repo. func getRecentCommitsDisplay(repo *git.Repository, headHash plumbing.Hash, numCommits int) (recentCommits []commitDisplay, err error) { var commitIter object.CommitIter var thisCommit *object.Commit @@ -219,7 +221,12 @@ Message string TreeHash plumbing.Hash } -func fmtCommitAsPatch(commit *object.Commit) (parentCommitHash plumbing.Hash, patch *object.Patch, err error) { +// commitToPatch creates an [object.Patch] from the first parent of a given +// [object.Commit]. +// +// TODO: This function should be deprecated as it only diffs with the first +// parent and does not correctly handle merge commits. +func commitToPatch(commit *object.Commit) (parentCommitHash plumbing.Hash, patch *object.Patch, err error) { var parentCommit *object.Commit var commitTree *object.Tree diff --git a/http_auth.go b/http_auth.go index 8208daf9a102556316769d8681af088f11024234..03b7e2b24bf08cb03211f305a409f8292f80d739 100644 --- a/http_auth.go +++ b/http_auth.go @@ -7,6 +7,8 @@ import ( "net/http" ) +// getUserFromRequest returns the user ID and username associated with the +// session cookie in a given [http.Request]. func getUserFromRequest(request *http.Request) (id int, username string, err error) { var sessionCookie *http.Cookie diff --git a/http_global.go b/http_global.go index 02892c95a2d8fd2b3a0b6a22b81ba9bcb010ea9b..6e236a7fd81acd911adb9e24efb611352d9bf6fd 100644 --- a/http_global.go +++ b/http_global.go @@ -3,7 +3,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu package main -// globalData is passed as "global" when rendering HTML templates. +// globalData is passed as "global" when rendering HTML templates and contains +// global data that should stay constant throughout an execution of Lindenii +// Forge as no synchronization mechanism is provided for updating it. var globalData = map[string]any{ "server_public_key_string": &serverPubkeyString, "server_public_key_fingerprint": &serverPubkeyFP, diff --git a/http_handle_branches.go b/http_handle_branches.go index fa3bbab30ce32dc63684bdd45e227a2e2b1900c8..48ba5ab0a641a6512e6dcc66f7c7346211c5e985 100644 --- a/http_handle_branches.go +++ b/http_handle_branches.go @@ -12,6 +12,7 @@ "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" ) +// httpHandleRepoBranches provides the branches page in repos. func httpHandleRepoBranches(writer http.ResponseWriter, _ *http.Request, params map[string]any) { var repo *git.Repository var repoName string diff --git a/http_handle_gc.go b/http_handle_gc.go index 3322c77a899957f9cd6fd7fc0eeb85d39464a110..83d2e06a782a7ca178e3e2a5570e9e068011ba59 100644 --- a/http_handle_gc.go +++ b/http_handle_gc.go @@ -8,6 +8,11 @@ "net/http" "runtime" ) +// httpHandleGC handles an HTTP request by calling the garbage collector and +// redirecting the user back to the home page. +// +// TODO: This should probably be removed or hidden behind an administrator's +// control panel, in the future. func httpHandleGC(writer http.ResponseWriter, request *http.Request, _ map[string]any) { runtime.GC() http.Redirect(writer, request, "/", http.StatusSeeOther) diff --git a/http_handle_group_index.go b/http_handle_group_index.go index 500824660e67c0f16135da8ddd921f101bf97e8e..67cffd82eba58e1ccd5bedb76b45e1052fbfe345 100644 --- a/http_handle_group_index.go +++ b/http_handle_group_index.go @@ -13,6 +13,9 @@ "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) +// httpHandleGroupIndex provides index pages for groups, which includes a list +// of its subgroups and repos, as well as a form for group maintainers to +// create repos. func httpHandleGroupIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) { var groupPath []string var repos []nameDesc diff --git a/http_handle_index.go b/http_handle_index.go index 9840e83f263b2708035b7db15299a4c86dcd5022..5d2dc3e0aaac8d8717227f15e897f9b6e7c67720 100644 --- a/http_handle_index.go +++ b/http_handle_index.go @@ -10,6 +10,8 @@ "github.com/dustin/go-humanize" ) +// httpHandleIndex provides the main index page which includes a list of groups +// and some global information such as SSH keys. func httpHandleIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) { var err error var groups []nameDesc diff --git a/http_handle_login.go b/http_handle_login.go index 8a181337d95d71871414a79bd386d7b2ad8ce86d..56c0a82c1474585b6bd5d81298db3afd52a438d2 100644 --- a/http_handle_login.go +++ b/http_handle_login.go @@ -15,6 +15,7 @@ "github.com/alexedwards/argon2id" "github.com/jackc/pgx/v5" ) +// httpHandleLogin provides the login page for local users. func httpHandleLogin(writer http.ResponseWriter, request *http.Request, params map[string]any) { var username, password string var userID int diff --git a/http_handle_repo_commit.go b/http_handle_repo_commit.go index 7b967fa7f94bae539925368f3ee111faef284d61..338ef8d36f83e7d48bd216af7ed65f8d7680d50b 100644 --- a/http_handle_repo_commit.go +++ b/http_handle_repo_commit.go @@ -16,14 +16,16 @@ "github.com/go-git/go-git/v5/plumbing/object" "go.lindenii.runxiyu.org/lindenii-common/misc" ) -// The file patch type from go-git isn't really usable in HTML templates -// either. +// usableFilePatch is a [diff.FilePatch] that is structured in a way more +// friendly for use in HTML templates. type usableFilePatch struct { From diff.File To diff.File Chunks []usableChunk } +// usableChunk is a [diff.Chunk] that is structured in a way more friendly for +// use in HTML templates. type usableChunk struct { Operation diff.Operation Content string @@ -66,7 +68,7 @@ params["commit_object"] = commitObj params["commit_id"] = commitIDStr - parentCommitHash, patch, err = fmtCommitAsPatch(commitObj) + parentCommitHash, patch, err = commitToPatch(commitObj) if err != nil { errorPage500(writer, params, "Error getting patch from commit: "+err.Error()) return diff --git a/http_handle_repo_contrib_index.go b/http_handle_repo_contrib_index.go index c199bfa997bf1f2ff5ae5edd22d03b638b73076e..ee7b9561fc5424717f56d7a1a1eb5b926a3cf55d 100644 --- a/http_handle_repo_contrib_index.go +++ b/http_handle_repo_contrib_index.go @@ -9,12 +9,15 @@ "github.com/jackc/pgx/v5" ) +// idTitleStatus describes properties of a merge request that needs to be +// present in MR listings. type idTitleStatus struct { ID int Title string Status string } +// httpHandleRepoContribIndex provides an index to merge requests of a repo. func httpHandleRepoContribIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) { var rows pgx.Rows var result []idTitleStatus diff --git a/http_handle_repo_contrib_one.go b/http_handle_repo_contrib_one.go index 0fc56fae73ef758a8a1fc26b188599d35f3e26e3..dcd0e0df99f285dc684fa3a5d3b665fe23ee2146 100644 --- a/http_handle_repo_contrib_one.go +++ b/http_handle_repo_contrib_one.go @@ -12,6 +12,8 @@ "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) +// httpHandleRepoContribOne provides an interface to each merge request of a +// repo. func httpHandleRepoContribOne(writer http.ResponseWriter, request *http.Request, params map[string]any) { var mrIDStr string var mrIDInt int diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go index 2b84f89433e0705ebfb831c3c9d03657075957ac..9dc3ea6f60fc093e8598fbfba07f6e3200232e58 100644 --- a/http_handle_repo_index.go +++ b/http_handle_repo_index.go @@ -14,6 +14,7 @@ "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/storer" ) +// httpHandleRepoIndex provides the front page of a repo. func httpHandleRepoIndex(writer http.ResponseWriter, _ *http.Request, params map[string]any) { var repo *git.Repository var repoName string diff --git a/http_handle_repo_info.go b/http_handle_repo_info.go index c4562e5d8cf685c3ff3c9e8fef1c7cdf7c9997d5..3f1787eae2137b5d43a593d9ffc21ab88c059acf 100644 --- a/http_handle_repo_info.go +++ b/http_handle_repo_info.go @@ -12,6 +12,10 @@ "github.com/jackc/pgx/v5/pgtype" ) +// httpHandleRepoInfo provides advertised refs of a repo for use in Git's Smart +// HTTP protocol. +// +// TODO: Reject access from web browsers. func httpHandleRepoInfo(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) { groupPath := params["group_path"].([]string) repoName := params["repo_name"].(string) diff --git a/http_handle_repo_log.go b/http_handle_repo_log.go index 50e4eb156109f3acdb96aa7f6e47e5f651e43c64..5c69836d3d6f9407d169c5e61dd24bf0597382b6 100644 --- a/http_handle_repo_log.go +++ b/http_handle_repo_log.go @@ -10,7 +10,10 @@ "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" ) -// TODO: I probably shouldn't include *all* commits here... +// httpHandleRepoLog provides a page with a complete Git log. +// +// TODO: This currently provides all commits in the branch. It should be +// paginated and cached instead. func httpHandleRepoLog(writer http.ResponseWriter, _ *http.Request, params map[string]any) { var repo *git.Repository var refHash plumbing.Hash diff --git a/http_handle_repo_raw.go b/http_handle_repo_raw.go index 858f9121f0c0c641701284891f3c70f96aa50111..4394e1865d1594a010b58af839f8803df19813da 100644 --- a/http_handle_repo_raw.go +++ b/http_handle_repo_raw.go @@ -14,6 +14,8 @@ "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) +// httpHandleRepoRaw serves raw files, or directory listings that point to raw +// files. func httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) { var rawPathSpec, pathSpec string var repo *git.Repository diff --git a/http_handle_repo_tree.go b/http_handle_repo_tree.go index 714ecdd301c0998413c5b4660fc3591034301824..3e59dab68548a51f5ffbd9dd7534d96a946d4708 100644 --- a/http_handle_repo_tree.go +++ b/http_handle_repo_tree.go @@ -20,6 +20,10 @@ "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) +// 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) { var rawPathSpec, pathSpec string var repo *git.Repository diff --git a/http_handle_repo_upload_pack.go b/http_handle_repo_upload_pack.go index 6cea52810377a4127374bbaa5868fe6d2a995cd5..e201193b9b8e038ffd6bdcc7b00e34fc059119e0 100644 --- a/http_handle_repo_upload_pack.go +++ b/http_handle_repo_upload_pack.go @@ -12,6 +12,8 @@ "github.com/jackc/pgx/v5/pgtype" ) +// httpHandleUploadPack handles incoming Git fetch/pull/clone's over the Smart +// HTTP protocol. func httpHandleUploadPack(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) { var groupPath []string var repoName string diff --git a/http_handle_users.go b/http_handle_users.go index f68f3c2ad883d0ad1a7f4676b101e287104ec30d..e02d4b280775ac3da7563e78b15b8b66bd411149 100644 --- a/http_handle_users.go +++ b/http_handle_users.go @@ -7,6 +7,7 @@ import ( "net/http" ) +// httpHandleUsers is a useless stub. func httpHandleUsers(writer http.ResponseWriter, _ *http.Request, params map[string]any) { errorPage501(writer, params) } diff --git a/http_server.go b/http_server.go index 2b1f77a57f353a80ee4c4cb6b2267f506dd5e9b1..65200379c2602cb7e7c6fcd1b0af1a9532654a91 100644 --- a/http_server.go +++ b/http_server.go @@ -15,6 +15,10 @@ ) type forgeHTTPRouter struct{} +// ServeHTTP handles all incoming HTTP requests and routes them to the correct +// location. +// +// TODO: This function is way too large. func (router *forgeHTTPRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var remoteAddr string if config.HTTP.ReverseProxy { diff --git a/http_template_funcs.go b/http_template_funcs.go index f466f07fa2681ad6fbe3130f45dddfd07ce3982a..526841f1de10f3c33efd01187a2a79fcf7e101ff 100644 --- a/http_template_funcs.go +++ b/http_template_funcs.go @@ -5,31 +5,35 @@ package main import ( "net/url" - "path" "strings" ) +// These are all trivial functions that are used in HTML templates. +// See resources.go. + +// firstLine returns the first line of a string. func firstLine(s string) string { before, _, _ := strings.Cut(s, "\n") return before } -func baseName(s string) string { - return path.Base(s) -} - +// pathEscape escapes the input as an URL path segment. func pathEscape(s string) string { return url.PathEscape(s) } +// queryEscape escapes the input as an URL query segment. func queryEscape(s string) string { return url.QueryEscape(s) } +// dereference dereferences a pointer. func dereference[T any](p *T) T { return *p } +// dereferenceOrZero dereferences a pointer. If the pointer is nil, the zero +// value of its associated type is returned instead. func dereferenceOrZero[T any](p *T) T { if p != nil { return *p @@ -38,6 +42,7 @@ var z T return z } +// minus subtracts two numbers. func minus(a, b int) int { return a - b } diff --git a/irc.go b/irc.go index dac9379c935d3d36e283fdda871aba3ff87bb558..073bfad84f55e7ff7c842d12f661932750ba9437 100644 --- a/irc.go +++ b/irc.go @@ -120,6 +120,8 @@ } } } +// ircSendDirect sends an IRC message directly to the connection and bypasses +// the buffering system. func ircSendDirect(s string) error { ech := make(chan error, 1) @@ -131,6 +133,7 @@ return <-ech } +// TODO: Delay and warnings? func ircBotLoop() { ircSendBuffered = make(chan string, config.IRC.SendQ) ircSendDirectChan = make(chan errorBack[string]) diff --git a/iter.go b/iter.go new file mode 100644 index 0000000000000000000000000000000000000000..d4c7175ec6a1b6b41443da9461a4f292a20ad5bc --- /dev/null +++ b/iter.go @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu + +package main + +import "iter" + +// iterSeqLimit returns an iterator equivalent to the supplied one, but stops +// after n iterations. +func iterSeqLimit[T any](s iter.Seq[T], n uint) iter.Seq[T] { + return func(yield func(T) bool) { + var iterations uint + for v := range s { + if iterations > n-1 { + return + } + if !yield(v) { + return + } + iterations++ + } + } +} diff --git a/readme_to_html.go b/readme_to_html.go index 22f23a18e799efceba64d93d1f4c916b86bae080..6d7bbbcd3a5bcd88ff6445ce8001c4f344468c3e 100644 --- a/readme_to_html.go +++ b/readme_to_html.go @@ -18,6 +18,8 @@ ) var markdownConverter = goldmark.New(goldmark.WithExtensions(extension.GFM)) +// renderReadmeAtTree looks for README files in the supplied Git tree and +// returns its filename and rendered (and sanitized) HTML. func renderReadmeAtTree(tree *object.Tree) (readmeFilename string, readmeRenderedSafeHTML template.HTML) { var readmeRenderedUnsafe bytes.Buffer var readmeFile *object.File @@ -60,6 +62,7 @@ return "", "" } +// 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/remote_url.go b/remote_url.go index 9ddc54878741b6218333e17f3c2dfdfc4e3bd2cf..06ee0da3d370687b0c14a2bdcbb1844061fd7221 100644 --- a/remote_url.go +++ b/remote_url.go @@ -10,10 +10,14 @@ ) // We don't use path.Join because it collapses multiple slashes into one. +// genSSHRemoteURL generates SSH remote URLs from a given group path and repo +// name. func genSSHRemoteURL(groupPath []string, repoName string) string { return strings.TrimSuffix(config.SSH.Root, "/") + "/" + segmentsToURL(groupPath) + "/:/repos/" + url.PathEscape(repoName) } +// genHTTPRemoteURL generates HTTP remote URLs from a given group path and repo +// name. func genHTTPRemoteURL(groupPath []string, repoName string) string { return strings.TrimSuffix(config.HTTP.Root, "/") + "/" + segmentsToURL(groupPath) + "/:/repos/" + url.PathEscape(repoName) } diff --git a/resources.go b/resources.go index 015f33fe412257ffb7192f011a3849a133de926b..567976d786a41856ce39873df23e06600ea9787d 100644 --- a/resources.go +++ b/resources.go @@ -26,6 +26,7 @@ var resourcesFS embed.FS var templates *template.Template +// loadTemplates minifies and loads HTML templates. func loadTemplates() (err error) { minifier := minify.New() minifierOptions := html.Minifier{ @@ -36,7 +37,6 @@ 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], @@ -73,6 +73,8 @@ staticHandler http.Handler manHandler http.Handler ) +// This init sets up static and man handlers. The resulting handlers must be +// used in the HTTP router, and do nothing unless called from elsewhere. func init() { staticFS, err := fs.Sub(resourcesFS, "static") if err != nil { diff --git a/ssh_handle_receive_pack.go b/ssh_handle_receive_pack.go index 0b3c19affb0bab50fc7fb5c3c33aeac08b487c8d..f8777fadd91a9b968679dd21a950283da88d040e 100644 --- a/ssh_handle_receive_pack.go +++ b/ssh_handle_receive_pack.go @@ -14,6 +14,8 @@ "github.com/go-git/go-git/v5" "go.lindenii.runxiyu.org/lindenii-common/cmap" ) +// packPass contains information known when handling incoming SSH connections +// that then needs to be used in hook socket connection handlers. See hookc(1). type packPass struct { session gliderSSH.Session repo *git.Repository @@ -28,6 +30,7 @@ repoName string contribReq string } +// packPasses contains hook cookies mapped to their packPass. var packPasses = cmap.Map[string, packPass]{} // sshHandleRecvPack handles attempts to push to repos. diff --git a/ssh_server.go b/ssh_server.go index 8afbd2aed18dad0c009c340a2417d74dede5c08d..67477e7c1fba9ae26f451d06695fe6c9f6c3b20f 100644 --- a/ssh_server.go +++ b/ssh_server.go @@ -21,6 +21,9 @@ serverPubkeyFP string serverPubkey goSSH.PublicKey ) +// serveSSH serves SSH on a [net.Listener]. The listener should generally be a +// TCP listener, although AF_UNIX SOCK_STREAM listeners may be appropriate in +// rare cases. func serveSSH(listener net.Listener) error { var hostKeyBytes []byte var hostKey goSSH.Signer diff --git a/ssh_utils.go b/ssh_utils.go index ef6b33bb9882172f94eea70a99f0fd9f07bd3289..94aabe4cf954f495f1fd52f1991597b8bf581c48 100644 --- a/ssh_utils.go +++ b/ssh_utils.go @@ -16,6 +16,8 @@ ) var errIllegalSSHRepoPath = errors.New("illegal SSH repo path") +// getRepoInfo2 also fetches repo information... it should be deprecated and +// implemented in individual handlers. func getRepoInfo2(ctx context.Context, sshPath, sshPubkey string) (groupPath []string, repoName string, repoID int, repoPath string, directAccess bool, contribReq, userType string, userID int, err error) { var segments []string var sepIndex int @@ -66,6 +68,9 @@ return []string{}, "", 0, "", false, "", "", 0, errIllegalSSHRepoPath } } +// writeRedError is a helper function that basically does a Fprintf but makes +// the entire thing red, in terms of ANSI escape sequences. It's useful when +// producing error messages on SSH connections. func writeRedError(w io.Writer, format string, args ...any) { fmt.Fprintln(w, ansiec.Red+fmt.Sprintf(format, args...)+ansiec.Reset) } diff --git a/url.go b/url.go index f415c2dd46bdb95e055a5e72ddb8e68852e95cde..b9c575325ab48e3dfdf0282013a5f7b6da7f4024 100644 --- a/url.go +++ b/url.go @@ -15,6 +15,8 @@ errDupRefSpec = errors.New("duplicate ref spec") errNoRefSpec = errors.New("no ref spec") ) +// getParamRefTypeName looks at the query parameters in an HTTP request and +// returns its ref name and type, if any. func getParamRefTypeName(request *http.Request) (retRefType, retRefName string, err error) { rawQuery := request.URL.RawQuery queryValues, err := url.ParseQuery(rawQuery) @@ -44,6 +46,8 @@ } return } +// parseReqURI parses an HTTP request URL, and returns a slice of path segments +// and the query parameters. It handles %2F correctly. func parseReqURI(requestURI string) (segments []string, params url.Values, err error) { path, paramsStr, _ := strings.Cut(requestURI, "?") @@ -60,6 +64,9 @@ params, err = url.ParseQuery(paramsStr) return } +// redirectDir returns true and redirects the user to a version of the URL with +// a trailing slash, if and only if the request URL does not already have a +// trailing slash. func redirectDir(writer http.ResponseWriter, request *http.Request) bool { requestURI := request.RequestURI @@ -79,6 +86,9 @@ } return false } +// redirectNoDir returns true and redirects the user to a version of the URL +// without a trailing slash, if and only if the request URL has a trailing +// slash. func redirectNoDir(writer http.ResponseWriter, request *http.Request) bool { requestURI := request.RequestURI @@ -98,6 +108,8 @@ } return false } +// redirectUnconditionally unconditionally redirects the user back to the +// current page while preserving query parameters. func redirectUnconditionally(writer http.ResponseWriter, request *http.Request) { requestURI := request.RequestURI @@ -113,6 +125,8 @@ http.Redirect(writer, request, path+rest, http.StatusSeeOther) } +// segmentsToURL joins URL segments to the path component of a URL. +// Each segment is escaped properly first. func segmentsToURL(segments []string) string { for i, segment := range segments { segments[i] = url.PathEscape(segment) @@ -120,6 +134,7 @@ } return strings.Join(segments, "/") } +// anyContain returns true if and only if ss contains a string that contains c. func anyContain(ss []string, c string) bool { for _, s := range ss { if strings.Contains(s, c) { diff --git a/users.go b/users.go index 5f5a4b2066cae73ae8d86245de6099832cb8a7f1..f0dabce3de9018098bacd3c30aa5598486dee2e2 100644 --- a/users.go +++ b/users.go @@ -9,6 +9,9 @@ "github.com/jackc/pgx/v5" ) +// addUserSSH adds a new user solely based on their SSH public key. +// +// TODO: Audit all users of this function. func addUserSSH(ctx context.Context, pubkey string) (userID int, err error) { var txn pgx.Tx diff --git a/utils.go b/utils.go index bac82323581f28666a5a2d0ae47b2e7a322833cf..8ede372a301e54821e71f04f53b1b5d126c7cf50 100644 --- a/utils.go +++ b/utils.go @@ -5,6 +5,8 @@ package main import "strings" +// sliceContainsNewlines returns true if and only if the given slice contains +// one or more strings that contains newlines. func sliceContainsNewlines(s []string) bool { for _, v := range s { if strings.Contains(v, "\n") { -- 2.48.1