aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--acl.go6
-rw-r--r--cache_commit_path_file_html.go1
-rw-r--r--cache_commit_path_file_raw.go1
-rw-r--r--cache_commit_tree_readme.go1
-rw-r--r--cache_index_commits_display.go1
-rw-r--r--config.go18
-rw-r--r--database.go15
-rw-r--r--fedauth.go4
-rw-r--r--git_format_patch.go4
-rw-r--r--git_hooks_deploy.go6
-rw-r--r--git_hooks_handle_linux.go9
-rw-r--r--git_hooks_handle_other.go7
-rw-r--r--git_init.go4
-rw-r--r--git_misc.go39
-rw-r--r--http_auth.go2
-rw-r--r--http_global.go4
-rw-r--r--http_handle_branches.go1
-rw-r--r--http_handle_gc.go5
-rw-r--r--http_handle_group_index.go3
-rw-r--r--http_handle_index.go2
-rw-r--r--http_handle_login.go1
-rw-r--r--http_handle_repo_commit.go8
-rw-r--r--http_handle_repo_contrib_index.go3
-rw-r--r--http_handle_repo_contrib_one.go2
-rw-r--r--http_handle_repo_index.go1
-rw-r--r--http_handle_repo_info.go4
-rw-r--r--http_handle_repo_log.go5
-rw-r--r--http_handle_repo_raw.go2
-rw-r--r--http_handle_repo_tree.go4
-rw-r--r--http_handle_repo_upload_pack.go2
-rw-r--r--http_handle_users.go1
-rw-r--r--http_server.go4
-rw-r--r--http_template_funcs.go15
-rw-r--r--irc.go3
-rw-r--r--iter.go23
-rw-r--r--readme_to_html.go3
-rw-r--r--remote_url.go4
-rw-r--r--resources.go4
-rw-r--r--ssh_handle_receive_pack.go3
-rw-r--r--ssh_server.go3
-rw-r--r--ssh_utils.go5
-rw-r--r--url.go15
-rw-r--r--users.go3
-rw-r--r--utils.go2
44 files changed, 211 insertions, 42 deletions
diff --git a/acl.go b/acl.go
index 0595d55..5f242d5 100644
--- a/acl.go
+++ b/acl.go
@@ -9,8 +9,10 @@ import (
"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 944a6e7..9254d6b 100644
--- a/cache_commit_path_file_html.go
+++ b/cache_commit_path_file_html.go
@@ -10,6 +10,7 @@ import (
"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 bd7cc7e..89b817b 100644
--- a/cache_commit_path_file_raw.go
+++ b/cache_commit_path_file_raw.go
@@ -8,6 +8,7 @@ import (
"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 de58d71..afd7f3e 100644
--- a/cache_commit_tree_readme.go
+++ b/cache_commit_tree_readme.go
@@ -16,6 +16,7 @@ type treeReadmeCacheEntry struct {
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 786c3d3..fc28cf1 100644
--- a/cache_index_commits_display.go
+++ b/cache_index_commits_display.go
@@ -8,6 +8,7 @@ import (
"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 8bf05d9..1973f3d 100644
--- a/config.go
+++ b/config.go
@@ -13,8 +13,9 @@ import (
"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 @@ var config struct {
} `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 5205214..87fa9f4 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 ef1b5ec..808fba5 100644
--- a/fedauth.go
+++ b/fedauth.go
@@ -15,6 +15,8 @@ import (
"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 @@ func fedauth(ctx context.Context, userID int, service, remoteUsername, pubkey st
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 5b5e04e..79a7474 100644
--- a/git_format_patch.go
+++ b/git_format_patch.go
@@ -12,7 +12,7 @@ import (
"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 @@ func fmtCommitPatch(commit *object.Commit) (final string, err error) {
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 eeb043e..c9039fe 100644
--- a/git_hooks_deploy.go
+++ b/git_hooks_deploy.go
@@ -11,9 +11,9 @@ import (
"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 812a429..e316bb7 100644
--- a/git_hooks_handle_linux.go
+++ b/git_hooks_handle_linux.go
@@ -337,6 +337,10 @@ func hooksHandler(conn net.Conn) {
_, _ = 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 @@ func serveGitHooks(listener net.Listener) error {
}
}
+// 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 @@ func getUcred(conn net.Conn) (ucred *syscall.Ucred, err error) {
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 f1b2e04..4285784 100644
--- a/git_hooks_handle_other.go
+++ b/git_hooks_handle_other.go
@@ -315,6 +315,10 @@ func hooksHandler(conn net.Conn) {
_, _ = 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 @@ func serveGitHooks(listener net.Listener) error {
}
}
+// 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 3ea4f58..f1a283e 100644
--- a/git_init.go
+++ b/git_init.go
@@ -9,8 +9,8 @@ import (
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 7a5e7e2..a8d7c30 100644
--- a/git_misc.go
+++ b/git_misc.go
@@ -18,6 +18,10 @@ import (
)
// 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 @@ WHERE g.depth = cardinality($1::text[])
}
// 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 @@ type displayTreeEntry struct {
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 @@ func makeDisplayTree(tree *object.Tree) (displayTree []displayTreeEntry) {
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 @@ func commitIterSeqErr(commitIter object.CommitIter) (iter.Seq[*object.Commit], *
}, &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 @@ func getRecentCommits(repo *git.Repository, headHash plumbing.Hash, numCommits i
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 @@ type commitDisplay struct {
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 8208daf..03b7e2b 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 02892c9..6e236a7 100644
--- a/http_global.go
+++ b/http_global.go
@@ -3,7 +3,9 @@
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 fa3bbab..48ba5ab 100644
--- a/http_handle_branches.go
+++ b/http_handle_branches.go
@@ -12,6 +12,7 @@ import (
"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 3322c77..83d2e06 100644
--- a/http_handle_gc.go
+++ b/http_handle_gc.go
@@ -8,6 +8,11 @@ import (
"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 5008246..67cffd8 100644
--- a/http_handle_group_index.go
+++ b/http_handle_group_index.go
@@ -13,6 +13,9 @@ import (
"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 9840e83..5d2dc3e 100644
--- a/http_handle_index.go
+++ b/http_handle_index.go
@@ -10,6 +10,8 @@ import (
"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 8a18133..56c0a82 100644
--- a/http_handle_login.go
+++ b/http_handle_login.go
@@ -15,6 +15,7 @@ import (
"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 7b967fa..338ef8d 100644
--- a/http_handle_repo_commit.go
+++ b/http_handle_repo_commit.go
@@ -16,14 +16,16 @@ import (
"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 @@ func httpHandleRepoCommit(writer http.ResponseWriter, request *http.Request, par
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 c199bfa..ee7b956 100644
--- a/http_handle_repo_contrib_index.go
+++ b/http_handle_repo_contrib_index.go
@@ -9,12 +9,15 @@ import (
"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 0fc56fa..dcd0e0d 100644
--- a/http_handle_repo_contrib_one.go
+++ b/http_handle_repo_contrib_one.go
@@ -12,6 +12,8 @@ import (
"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 2b84f89..9dc3ea6 100644
--- a/http_handle_repo_index.go
+++ b/http_handle_repo_index.go
@@ -14,6 +14,7 @@ import (
"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 c4562e5..3f1787e 100644
--- a/http_handle_repo_info.go
+++ b/http_handle_repo_info.go
@@ -12,6 +12,10 @@ import (
"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 50e4eb1..5c69836 100644
--- a/http_handle_repo_log.go
+++ b/http_handle_repo_log.go
@@ -10,7 +10,10 @@ import (
"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 858f912..4394e18 100644
--- a/http_handle_repo_raw.go
+++ b/http_handle_repo_raw.go
@@ -14,6 +14,8 @@ import (
"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 714ecdd..3e59dab 100644
--- a/http_handle_repo_tree.go
+++ b/http_handle_repo_tree.go
@@ -20,6 +20,10 @@ import (
"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 6cea528..e201193 100644
--- a/http_handle_repo_upload_pack.go
+++ b/http_handle_repo_upload_pack.go
@@ -12,6 +12,8 @@ import (
"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 f68f3c2..e02d4b2 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 2b1f77a..6520037 100644
--- a/http_server.go
+++ b/http_server.go
@@ -15,6 +15,10 @@ import (
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 f466f07..526841f 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 @@ func dereferenceOrZero[T any](p *T) 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 dac9379..073bfad 100644
--- a/irc.go
+++ b/irc.go
@@ -120,6 +120,8 @@ func ircBotSession() error {
}
}
+// 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 @@ func ircSendDirect(s string) error {
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 0000000..d4c7175
--- /dev/null
+++ b/iter.go
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+
+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 22f23a1..6d7bbbc 100644
--- a/readme_to_html.go
+++ b/readme_to_html.go
@@ -18,6 +18,8 @@ import (
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 @@ func renderReadmeAtTree(tree *object.Tree) (readmeFilename string, readmeRendere
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 9ddc548..06ee0da 100644
--- a/remote_url.go
+++ b/remote_url.go
@@ -10,10 +10,14 @@ import (
// 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 015f33f..567976d 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 @@ func loadTemplates() (err error) {
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 @@ var (
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 0b3c19a..f8777fa 100644
--- a/ssh_handle_receive_pack.go
+++ b/ssh_handle_receive_pack.go
@@ -14,6 +14,8 @@ import (
"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 @@ type packPass struct {
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 8afbd2a..67477e7 100644
--- a/ssh_server.go
+++ b/ssh_server.go
@@ -21,6 +21,9 @@ var (
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 ef6b33b..94aabe4 100644
--- a/ssh_utils.go
+++ b/ssh_utils.go
@@ -16,6 +16,8 @@ import (
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 @@ func getRepoInfo2(ctx context.Context, sshPath, sshPubkey string) (groupPath []s
}
}
+// 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 f415c2d..b9c5753 100644
--- a/url.go
+++ b/url.go
@@ -15,6 +15,8 @@ var (
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 @@ func getParamRefTypeName(request *http.Request) (retRefType, retRefName string,
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 @@ func parseReqURI(requestURI string) (segments []string, params url.Values, err e
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 @@ func redirectDir(writer http.ResponseWriter, request *http.Request) bool {
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 @@ func redirectNoDir(writer http.ResponseWriter, request *http.Request) bool {
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 @@ func redirectUnconditionally(writer http.ResponseWriter, request *http.Request)
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 @@ func segmentsToURL(segments []string) string {
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 5f5a4b2..f0dabce 100644
--- a/users.go
+++ b/users.go
@@ -9,6 +9,9 @@ import (
"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 bac8232..8ede372 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") {