6 files changed,
68 insertions(+),
57 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-02-13 18:49:23 +0200
Authored at:
2026-02-13 17:35:23 +0200
Change ID:
nmqwpqklxkxxpytsrxvoxwsloswuktqs
Parent:
dc62a68
A
internal/git/paths.go
路路路 1 +package git 2 + 3 +import ( 4 + "fmt" 5 + "strings" 6 + 7 + securejoin "github.com/cyphar/filepath-securejoin" 8 +) 9 + 10 +func ResolveName(name string) string { 11 + return strings.TrimSuffix(name, ".git") + ".git" 12 +} 13 + 14 +func ResolvePath(baseDir, repoName string) (string, error) { 15 + path, err := securejoin.SecureJoin(baseDir, repoName) 16 + if err != nil { 17 + return "", fmt.Errorf("failed to secure join paths: %w", err) 18 + } 19 + return path, err 20 +}
M
internal/git/repo.go
路路路 20 20 var ( 21 21 ErrEmptyRepo = errors.New("repository has no commits") 22 22 ErrFileNotFound = errors.New("file not found") 23 + ErrPrivate = errors.New("repository is private") 23 24 ) 24 25 25 26 type Repo struct { 路路路 29 30 } 30 31 31 32 // Open opens a git repository at path. If ref is empty, HEAD is used. 32 -func Open(path string, ref string) (*Repo, error) { 33 +func Open(path, ref string) (*Repo, error) { 33 34 var err error 34 35 g := Repo{} 35 36 g.path = path 路路路 55 56 g.h = *hash 56 57 } 57 58 return &g, nil 59 +} 60 + 61 +// OpenPublic opens a repository, returns [ErrPrivate] if it's private. 62 +func OpenPublic(path, ref string) (*Repo, error) { 63 + r, err := Open(path, ref) 64 + if err != nil { 65 + return nil, err 66 + } 67 + 68 + isPrivate, err := r.IsPrivate() 69 + if err != nil { 70 + return nil, err 71 + } 72 + 73 + if isPrivate { 74 + return nil, ErrPrivate 75 + } 76 + 77 + return r, nil 58 78 } 59 79 60 80 func (g *Repo) IsEmpty() bool {
M
internal/handlers/feed.go
路路路 7 7 ) 8 8 9 9 func (h *handlers) repoFeedHandler(w http.ResponseWriter, r *http.Request) { 10 - name := getNormalizedName(r.PathValue("name")) 11 - repo, err := h.openPublicRepo(name, "") 10 + repo, err := h.openPublicRepo(r.PathValue("name"), "") 12 11 if err != nil { 13 12 h.write404(w, err) 14 13 return
M
internal/handlers/git.go
路路路 6 6 "log/slog" 7 7 "net/http" 8 8 9 - securejoin "github.com/cyphar/filepath-securejoin" 9 + "olexsmir.xyz/mugit/internal/git" 10 10 "olexsmir.xyz/mugit/internal/git/gitx" 11 11 ) 12 12 路路路 83 83 } 84 84 85 85 func (h *handlers) checkRepoPublicityAndGetPath(name string) (string, error) { 86 - repoPath := repoNameToPath(name) 87 - _, err := h.openPublicRepo(name, "") 86 + name = git.ResolveName(name) 87 + path, err := git.ResolvePath(h.c.Repo.Dir, name) 88 88 if err != nil { 89 89 return "", err 90 90 } 91 91 92 - path, err := securejoin.SecureJoin(h.c.Repo.Dir, repoPath) 93 - if err != nil { 92 + if _, err := git.OpenPublic(path, ""); err != nil { 94 93 return "", err 95 94 } 96 95 97 - return path, nil 96 + return path, err 97 +} 98 + 99 +func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { 100 + name = git.ResolveName(name) 101 + path, err := git.ResolvePath(h.c.Repo.Dir, name) 102 + if err != nil { 103 + return nil, err 104 + } 105 + return git.OpenPublic(path, ref) 98 106 } 99 107 100 108 type flushWriter struct {
M
internal/handlers/handlers.go
路路路 26 26 mux := http.NewServeMux() 27 27 mux.HandleFunc("GET /", h.indexHandler) 28 28 mux.HandleFunc("GET /index.xml", h.indexFeedHandler) 29 - mux.HandleFunc("GET /static/{file}", h.serveStatic) 29 + mux.HandleFunc("GET /static/{file}", h.serveStaticHandler) 30 30 mux.HandleFunc("GET /{name}", h.multiplex) 31 31 mux.HandleFunc("POST /{name}", h.multiplex) 32 32 mux.HandleFunc("GET /{name}/{rest...}", h.multiplex) 路路路 43 43 return h.loggingMiddleware(handler) 44 44 } 45 45 46 -func (h *handlers) serveStatic(w http.ResponseWriter, r *http.Request) { 46 +func (h *handlers) serveStaticHandler(w http.ResponseWriter, r *http.Request) { 47 47 f := filepath.Clean(r.PathValue("file")) 48 48 http.ServeFileFS(w, r, web.StaticFS, f) 49 -} 50 - 51 -func repoNameToPath(name string) string { return name + ".git" } 52 -func getNormalizedName(name string) string { 53 - return strings.TrimSuffix(name, ".git") 54 49 } 55 50 56 51 var templateFuncs = template.FuncMap{
M
internal/handlers/repo.go
路路路 15 15 "strings" 16 16 "time" 17 17 18 - securejoin "github.com/cyphar/filepath-securejoin" 19 18 "github.com/yuin/goldmark" 20 19 "github.com/yuin/goldmark/extension" 21 20 "github.com/yuin/goldmark/renderer/html" 路路路 43 42 )) 44 43 45 44 func (h *handlers) repoIndex(w http.ResponseWriter, r *http.Request) { 46 - name := getNormalizedName(r.PathValue("name")) 47 - repo, err := h.openPublicRepo(name, "") 45 + repo, err := h.openPublicRepo(r.PathValue("name"), "") 48 46 if err != nil { 49 47 h.write404(w, err) 50 48 return 路路路 57 55 } 58 56 59 57 data := make(map[string]any) 60 - data["name"] = name 58 + data["name"] = repo.Name() 61 59 data["desc"] = desc 62 60 data["servername"] = h.c.Meta.Host 63 61 data["meta"] = h.c.Meta 路路路 123 121 } 124 122 125 123 func (h *handlers) repoTreeHandler(w http.ResponseWriter, r *http.Request) { 126 - name := getNormalizedName(r.PathValue("name")) 124 + name := r.PathValue("name") 127 125 ref := r.PathValue("ref") 128 126 treePath := r.PathValue("rest") 129 127 路路路 158 156 } 159 157 160 158 func (h *handlers) fileContentsHandler(w http.ResponseWriter, r *http.Request) { 161 - name := getNormalizedName(r.PathValue("name")) 159 + name := r.PathValue("name") 162 160 ref := r.PathValue("ref") 163 161 treePath := r.PathValue("rest") 164 162 路路路 222 220 } 223 221 224 222 func (h *handlers) logHandler(w http.ResponseWriter, r *http.Request) { 225 - name := getNormalizedName(r.PathValue("name")) 223 + name := r.PathValue("name") 226 224 ref := r.PathValue("ref") 227 225 228 226 repo, err := h.openPublicRepo(name, ref) 路路路 254 252 } 255 253 256 254 func (h *handlers) commitHandler(w http.ResponseWriter, r *http.Request) { 257 - name := getNormalizedName(r.PathValue("name")) 255 + name := r.PathValue("name") 258 256 ref := r.PathValue("ref") 259 257 repo, err := h.openPublicRepo(name, ref) 260 258 if err != nil { 路路路 286 284 } 287 285 288 286 func (h *handlers) refsHandler(w http.ResponseWriter, r *http.Request) { 289 - name := getNormalizedName(r.PathValue("name")) 290 - repo, err := h.openPublicRepo(name, "") 287 + repo, err := h.openPublicRepo(r.PathValue("name"), "") 291 288 if err != nil { 292 289 h.write404(w, err) 293 290 return 路路路 319 316 320 317 data := make(map[string]any) 321 318 data["meta"] = h.c.Meta 322 - data["name"] = name 319 + data["name"] = repo.Name() 323 320 data["desc"] = desc 324 321 data["ref"] = masterBranch 325 322 data["branches"] = branches 路路路 353 350 } 354 351 } 355 352 356 -var errPrivateRepo = errors.New("private err") 357 - 358 -func (h *handlers) openPublicRepo(name, ref string) (*git.Repo, error) { 359 - // Convert normalized name back to filesystem path with .git suffix 360 - name = repoNameToPath(name) 361 - 362 - path, err := securejoin.SecureJoin(h.c.Repo.Dir, name) 363 - if err != nil { 364 - return nil, err 365 - } 366 - 367 - repo, err := git.Open(path, ref) 368 - if err != nil { 369 - return nil, err 370 - } 371 - 372 - isPrivate, err := repo.IsPrivate() 373 - if err != nil { 374 - return nil, err 375 - } 376 - if isPrivate { 377 - return nil, errPrivateRepo 378 - } 379 - 380 - return repo, nil 381 -} 382 - 383 353 type repoList struct { 384 354 Name string 385 355 Desc string 路路路 400 370 } 401 371 402 372 name := dir.Name() 403 - normalizedName := getNormalizedName(name) 404 - repo, err := h.openPublicRepo(normalizedName, "") 373 + repo, err := h.openPublicRepo(name, "") 405 374 if err != nil { 406 375 // if it's not git repo, just ignore it 407 376 continue 路路路 420 389 } 421 390 422 391 repos = append(repos, repoList{ 423 - Name: normalizedName, 392 + Name: repo.Name(), 424 393 Desc: desc, 425 394 LastCommit: lastCommit.Committed, 426 395 })