155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ---------------- Git HTTP ----------------
|
|
|
|
func (s *Server) handleGitHTTP(w http.ResponseWriter, r *http.Request) {
|
|
owner, repoName, _, ok := parseGitPath(r.URL.Path)
|
|
if !ok {
|
|
writeError(w, http.StatusNotFound, "invalid git path")
|
|
return
|
|
}
|
|
repo, err := s.loadRepo(owner, repoName)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "repository not found")
|
|
return
|
|
}
|
|
service := gitService(r)
|
|
user, authed := s.userFromBasic(r)
|
|
if service == "git-receive-pack" {
|
|
if !authed {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="gitocean"`)
|
|
writeError(w, http.StatusUnauthorized, "authentication required")
|
|
return
|
|
}
|
|
if repo.Archived {
|
|
writeError(w, http.StatusForbidden, "repository is archived")
|
|
return
|
|
}
|
|
if !s.canWriteRepo(repo, user) {
|
|
writeError(w, http.StatusForbidden, "write access required")
|
|
return
|
|
}
|
|
} else {
|
|
if repo.Visibility == "private" {
|
|
if !authed {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="gitocean"`)
|
|
writeError(w, http.StatusUnauthorized, "authentication required")
|
|
return
|
|
}
|
|
if !s.canReadRepo(repo, user, authed) {
|
|
writeError(w, http.StatusForbidden, "not allowed")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
s.runGitHTTPBackend(w, r, authed, user.Username)
|
|
}
|
|
|
|
func parseGitPath(path string) (owner, repo, rest string, ok bool) {
|
|
path = strings.TrimPrefix(path, "/")
|
|
idx := strings.Index(path, ".git")
|
|
if idx < 0 {
|
|
return "", "", "", false
|
|
}
|
|
repoPart := path[:idx]
|
|
rest = path[idx+len(".git"):]
|
|
parts := strings.Split(repoPart, "/")
|
|
if len(parts) != 2 {
|
|
return "", "", "", false
|
|
}
|
|
return parts[0], parts[1], rest, true
|
|
}
|
|
|
|
func gitService(r *http.Request) string {
|
|
if svc := r.URL.Query().Get("service"); svc != "" {
|
|
return svc
|
|
}
|
|
if strings.HasSuffix(r.URL.Path, "/git-receive-pack") {
|
|
return "git-receive-pack"
|
|
}
|
|
return "git-upload-pack"
|
|
}
|
|
|
|
func (s *Server) runGitHTTPBackend(w http.ResponseWriter, r *http.Request, authed bool, username string) {
|
|
root, _ := filepath.Abs(filepath.Join(s.storage, "repos"))
|
|
env := append(os.Environ(),
|
|
"GIT_PROJECT_ROOT="+root,
|
|
"GIT_HTTP_EXPORT_ALL=1",
|
|
"PATH_INFO="+r.URL.Path,
|
|
"REQUEST_METHOD="+r.Method,
|
|
"QUERY_STRING="+r.URL.RawQuery,
|
|
"REMOTE_ADDR="+r.RemoteAddr,
|
|
)
|
|
if ct := r.Header.Get("Content-Type"); ct != "" {
|
|
env = append(env, "CONTENT_TYPE="+ct)
|
|
}
|
|
if r.ContentLength >= 0 {
|
|
env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", r.ContentLength))
|
|
}
|
|
if authed {
|
|
env = append(env, "REMOTE_USER="+username, "AUTH_TYPE=Basic")
|
|
}
|
|
cmd := exec.Command("git", "http-backend")
|
|
cmd.Env = env
|
|
cmd.Stdin = r.Body
|
|
var out, errBuf bytes.Buffer
|
|
cmd.Stdout = &out
|
|
cmd.Stderr = &errBuf
|
|
if err := cmd.Run(); err != nil {
|
|
writeError(w, http.StatusInternalServerError, strings.TrimSpace(errBuf.String()))
|
|
return
|
|
}
|
|
writeCGIResponse(w, out.Bytes())
|
|
}
|
|
|
|
func writeCGIResponse(w http.ResponseWriter, data []byte) {
|
|
sep := []byte("\r\n\r\n")
|
|
idx := bytes.Index(data, sep)
|
|
if idx < 0 {
|
|
sep = []byte("\n\n")
|
|
idx = bytes.Index(data, sep)
|
|
}
|
|
if idx < 0 {
|
|
_, _ = w.Write(data)
|
|
return
|
|
}
|
|
headers := string(data[:idx])
|
|
body := data[idx+len(sep):]
|
|
status := http.StatusOK
|
|
for _, line := range strings.Split(headers, "\n") {
|
|
line = strings.TrimRight(line, "\r")
|
|
if line == "" {
|
|
continue
|
|
}
|
|
k, v, ok := strings.Cut(line, ":")
|
|
if !ok {
|
|
continue
|
|
}
|
|
k = strings.TrimSpace(k)
|
|
v = strings.TrimSpace(v)
|
|
if strings.EqualFold(k, "Status") {
|
|
fields := strings.Fields(v)
|
|
if len(fields) > 0 {
|
|
if n, err := strconv.Atoi(fields[0]); err == nil {
|
|
status = n
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
w.Header().Add(k, v)
|
|
}
|
|
w.WriteHeader(status)
|
|
_, _ = w.Write(body)
|
|
}
|