6 files changed,
68 insertions(+),
57 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-02-13 18:49:23 +0200
Change ID:
nmqwpqklxkxxpytsrxvoxwsloswuktqs
Parent:
dc62a68
A
internal/git/paths.go
@@ -0,0 +1,20 @@
+package git + +import ( + "fmt" + "strings" + + securejoin "github.com/cyphar/filepath-securejoin" +) + +func ResolveName(name string) string { + return strings.TrimSuffix(name, ".git") + ".git" +} + +func ResolvePath(baseDir, repoName string) (string, error) { + path, err := securejoin.SecureJoin(baseDir, repoName) + if err != nil { + return "", fmt.Errorf("failed to secure join paths: %w", err) + } + return path, err +}
M
internal/git/repo.go
@@ -20,6 +20,7 @@
var ( ErrEmptyRepo = errors.New("repository has no commits") ErrFileNotFound = errors.New("file not found") + ErrPrivate = errors.New("repository is private") ) type Repo struct {@@ -29,7 +30,7 @@ h plumbing.Hash
} // Open opens a git repository at path. If ref is empty, HEAD is used. -func Open(path string, ref string) (*Repo, error) { +func Open(path, ref string) (*Repo, error) { var err error g := Repo{} g.path = path@@ -55,6 +56,25 @@ }
g.h = *hash } return &g, nil +} + +// OpenPublic opens a repository, returns [ErrPrivate] if it's private. +func OpenPublic(path, ref string) (*Repo, error) { + r, err := Open(path, ref) + if err != nil { + return nil, err + } + + isPrivate, err := r.IsPrivate() + if err != nil { + return nil, err + } + + if isPrivate { + return nil, ErrPrivate + } + + return r, nil } func (g *Repo) IsEmpty() bool {
M
internal/handlers/feed.go
@@ -7,8 +7,7 @@ "github.com/gorilla/feeds"
) func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return
M
internal/handlers/git.go
@@ -6,7 +6,7 @@ "io"
"log/slog" "net/http" - securejoin "github.com/cyphar/filepath-securejoin" + "olexsmir.xyz/mugit/internal/git" "olexsmir.xyz/mugit/internal/git/gitx" )@@ -83,18 +83,26 @@ }
} func (h *handlers) checkRepoPublicityAndGetPath(name string) (string, error) { - repoPath := repoNameToPath(name) - _, err := h.openPublicRepo(name, "") + name = git.ResolveName(name) + path, err := git.ResolvePath(h.c.Repo.Dir, name) if err != nil { return "", err } - path, err := securejoin.SecureJoin(h.c.Repo.Dir, repoPath) - if err != nil { + if _, err := git.OpenPublic(path, ""); err != nil { return "", err } - return path, nil + return path, err +} + +func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { + name = git.ResolveName(name) + path, err := git.ResolvePath(h.c.Repo.Dir, name) + if err != nil { + return nil, err + } + return git.OpenPublic(path, ref) } type flushWriter struct {
M
internal/handlers/handlers.go
@@ -26,7 +26,7 @@
mux := http.NewServeMux() mux.HandleFunc("GET /", h.indexHandler) mux.HandleFunc("GET /index.xml", h.indexFeedHandler) - mux.HandleFunc("GET /static/{file}", h.serveStatic) + mux.HandleFunc("GET /static/{file}", h.serveStaticHandler) mux.HandleFunc("GET /{name}", h.multiplex) mux.HandleFunc("POST /{name}", h.multiplex) mux.HandleFunc("GET /{name}/{rest...}", h.multiplex)@@ -43,14 +43,9 @@ handler := h.recoverMiddleware(mux)
return h.loggingMiddleware(handler) } -func (h *handlers) serveStatic(w http.ResponseWriter, r *http.Request) { +func (h *handlers) serveStaticHandler(w http.ResponseWriter, r *http.Request) { f := filepath.Clean(r.PathValue("file")) http.ServeFileFS(w, r, web.StaticFS, f) -} - -func repoNameToPath(name string) string { return name + ".git" } -func getNormalizedName(name string) string { - return strings.TrimSuffix(name, ".git") } var templateFuncs = template.FuncMap{
M
internal/handlers/repo.go
@@ -15,7 +15,6 @@ "strconv"
"strings" "time" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/renderer/html"@@ -43,8 +42,7 @@ extension.Linkify,
)) func (h *handlers) repoIndex(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return@@ -57,7 +55,7 @@ return
} data := make(map[string]any) - data["name"] = name + data["name"] = repo.Name() data["desc"] = desc data["servername"] = h.c.Meta.Host data["meta"] = h.c.Meta@@ -123,7 +121,7 @@ h.templ(w, "repo_index", data)
} func (h *handlers) repoTreeHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") treePath := r.PathValue("rest")@@ -158,7 +156,7 @@ h.templ(w, "repo_tree", data)
} func (h *handlers) fileContentsHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") treePath := r.PathValue("rest")@@ -222,7 +220,7 @@ h.templ(w, "repo_file", data)
} func (h *handlers) logHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") repo, err := h.openPublicRepo(name, ref)@@ -254,7 +252,7 @@ h.templ(w, "repo_log", data)
} func (h *handlers) commitHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) + name := r.PathValue("name") ref := r.PathValue("ref") repo, err := h.openPublicRepo(name, ref) if err != nil {@@ -286,8 +284,7 @@ h.templ(w, "repo_commit", data)
} func (h *handlers) refsHandler(w http.ResponseWriter, r *http.Request) { - name := getNormalizedName(r.PathValue("name")) - repo, err := h.openPublicRepo(name, "") + repo, err := h.openPublicRepo(r.PathValue("name"), "") if err != nil { h.write404(w, err) return@@ -319,7 +316,7 @@ }
data := make(map[string]any) data["meta"] = h.c.Meta - data["name"] = name + data["name"] = repo.Name() data["desc"] = desc data["ref"] = masterBranch data["branches"] = branches@@ -353,33 +350,6 @@ }
} } -var errPrivateRepo = errors.New("private err") - -func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { - // Convert normalized name back to filesystem path with .git suffix - name = repoNameToPath(name) - - path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) - if err != nil { - return nil, err - } - - repo, err := git.Open(path, ref) - if err != nil { - return nil, err - } - - isPrivate, err := repo.IsPrivate() - if err != nil { - return nil, err - } - if isPrivate { - return nil, errPrivateRepo - } - - return repo, nil -} - type repoList struct { Name string Desc string@@ -400,8 +370,7 @@ continue
} name := dir.Name() - normalizedName := getNormalizedName(name) - repo, err := h.openPublicRepo(normalizedName, "") + repo, err := h.openPublicRepo(name, "") if err != nil { // if it's not git repo, just ignore it continue@@ -420,7 +389,7 @@ continue
} repos = append(repos, repoList{ - Name: normalizedName, + Name: repo.Name(), Desc: desc, LastCommit: lastCommit.Committed, })