2 files changed,
61 insertions(+),
35 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-03-02 14:58:03 +0200
Authored at:
2026-03-02 14:16:26 +0200
Change ID:
vmzkklkkqyzluwzotxvolmtlmmlswvnk
Parent:
5957dc0
M
internal/handlers/git.go
路路路 1 1 package handlers 2 2 3 3 import ( 4 + "compress/gzip" 4 5 "fmt" 5 6 "io" 6 7 "log/slog" 路路路 10 11 "olexsmir.xyz/mugit/internal/git/gitx" 11 12 ) 12 13 13 -// multiplex, check if the request smells like gitprotocol-http(5), if so, it 14 -// passes it to git smart http, otherwise renders templates 15 -func (h *handlers) multiplex(w http.ResponseWriter, r *http.Request) { 16 - if r.URL.RawQuery == "service=git-receive-pack" { 17 - w.WriteHeader(http.StatusBadRequest) 18 - w.Write([]byte("http pushing is not supported")) 14 +func (h *handlers) infoRefsHandler(w http.ResponseWriter, r *http.Request) { 15 + path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") 16 + if err != nil { 17 + h.gitError(w, http.StatusNotFound, "repository not found") 19 18 return 20 19 } 21 20 22 - path := r.PathValue("rest") 23 - if path == "info/refs" && r.Method == "GET" && r.URL.RawQuery == "service=git-upload-pack" { 24 - h.infoRefs(w, r) 25 - } else if path == "git-upload-pack" && r.Method == "POST" { 26 - h.uploadPack(w, r) 27 - } else if r.Method == "GET" && path == "" { 28 - h.repoIndex(w, r) 29 - } else { 30 - h.write404(w, nil) 21 + service := r.URL.Query().Get("service") 22 + switch service { 23 + case "git-upload-pack": 24 + w.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement") 25 + w.Header().Set("Connection", "Keep-Alive") 26 + w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 27 + 28 + w.WriteHeader(http.StatusOK) 29 + if err := gitx.InfoRefs(r.Context(), path, w); err != nil { 30 + h.gitError(w, http.StatusInternalServerError, err.Error()) 31 + slog.Error("git: info/refs", "err", err) 32 + return 33 + } 34 + 35 + case "git-receive-pack": 36 + h.receivePackHandler(w, r) 37 + 38 + default: 39 + h.gitError(w, http.StatusBadRequest, "service unsupported") 31 40 } 32 41 } 33 42 34 -func (h *handlers) infoRefs(w http.ResponseWriter, r *http.Request) { 43 +const uploadPackExpectedContentType = "application/x-git-upload-pack-request" 44 + 45 +func (h *handlers) uploadPackHandler(w http.ResponseWriter, r *http.Request) { 35 46 path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") 36 47 if err != nil { 37 - h.write404(w, err) 48 + h.gitError(w, http.StatusNotFound, "repository not found") 38 49 return 39 50 } 40 51 41 - w.Header().Set("content-type", "application/x-git-upload-pack-advertisement") 42 - w.WriteHeader(http.StatusOK) 43 - if err := gitx.InfoRefs(r.Context(), path, w); err != nil { 44 - w.WriteHeader(http.StatusInternalServerError) 45 - slog.Error("git: info/refs", "err", err) 52 + contentType := r.Header.Get("Content-Type") 53 + if contentType != uploadPackExpectedContentType { 54 + h.gitError(w, http.StatusUnsupportedMediaType, "provided content type is not supported") 46 55 return 47 56 } 48 -} 49 57 50 -func (h *handlers) uploadPack(w http.ResponseWriter, r *http.Request) { 51 - path, err := h.checkRepoPublicityAndGetPath(r.PathValue("name"), "") 52 - if err != nil { 53 - h.write404(w, err) 54 - return 58 + bodyReader := r.Body 59 + if r.Header.Get("Content-Encoding") == "gzip" { 60 + gzipReader, err := gzip.NewReader(r.Body) 61 + if err != nil { 62 + h.gitError(w, http.StatusInternalServerError, err.Error()) 63 + slog.Error("git: failed to create gzip reader", "err", err) 64 + return 65 + } 66 + defer gzipReader.Close() 67 + bodyReader = gzipReader 55 68 } 56 69 57 70 w.Header().Set("Content-Type", "application/x-git-upload-pack-result") 58 - w.Header().Set("Cache-Control", "no-cache") 71 + w.Header().Set("Connection", "Keep-Alive") 72 + w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") 73 + 59 74 w.WriteHeader(http.StatusOK) 60 - 61 - if err := gitx.UploadPack(r.Context(), path, true, r.Body, newFlushWriter(w)); err != nil { 75 + if err := gitx.UploadPack(r.Context(), path, true, bodyReader, newFlushWriter(w)); err != nil { 76 + h.gitError(w, http.StatusInternalServerError, err.Error()) 62 77 slog.Error("git: upload-pack", "err", err) 63 78 return 79 + 64 80 } 81 +} 82 + 83 +func (h *handlers) receivePackHandler(w http.ResponseWriter, _ *http.Request) { 84 + h.gitError(w, http.StatusForbidden, "pushes are only supported over ssh") 65 85 } 66 86 67 87 func (h *handlers) archiveHandler(w http.ResponseWriter, r *http.Request) { 路路路 83 103 slog.Error("git: archive", "ref", ref, "err", err) 84 104 return 85 105 } 106 +} 107 + 108 +func (h *handlers) gitError(w http.ResponseWriter, code int, msg string) { 109 + w.Header().Set("content-type", "text/plain; charset=UTF-8") 110 + w.WriteHeader(code) 111 + fmt.Fprintf(w, "%s\n", msg) 86 112 } 87 113 88 114 func (h *handlers) checkRepoPublicityAndGetPath(name string, ref string) (string, error) {
M
internal/handlers/handlers.go
路路路 39 39 mux.HandleFunc("GET /", h.indexHandler) 40 40 mux.HandleFunc("GET /index.xml", h.indexFeedHandler) 41 41 mux.HandleFunc("GET /static/{file}", h.serveStaticHandler) 42 - mux.HandleFunc("GET /{name}", h.multiplex) 43 - mux.HandleFunc("POST /{name}", h.multiplex) 44 - mux.HandleFunc("GET /{name}/{rest...}", h.multiplex) 45 - mux.HandleFunc("POST /{name}/{rest...}", h.multiplex) 42 + mux.HandleFunc("GET /{name}", h.repoIndex) 43 + mux.HandleFunc("GET /{name}/info/refs", h.infoRefsHandler) 44 + mux.HandleFunc("POST /{name}/git-upload-pack", h.uploadPackHandler) 45 + mux.HandleFunc("POST /{name}/git-receive-pack", h.receivePackHandler) 46 46 mux.HandleFunc("GET /{name}/feed/{$}", h.repoFeedHandler) 47 47 mux.HandleFunc("GET /{name}/tree/{ref}/{rest...}", h.repoTreeHandler) 48 48 mux.HandleFunc("GET /{name}/blob/{ref}/{rest...}", h.fileContentsHandler)