git_http.go 2.5 KiB raw
1
package main
2
3
import (
4
	"net/http"
5
	"net/http/cgi"
6
	"os/exec"
7
	"strings"
8
)
9
10
const (
11
	gitHTTPDeltaBaseCacheLimit = "32m"
12
	gitHTTPMaxConcurrent       = 8
13
)
14
15
func gitHTTPEnv() []string {
16
	return []string{
17
		"GIT_PROJECT_ROOT=",
18
		"GIT_HTTP_EXPORT_ALL=1",
19
		"GIT_CONFIG_COUNT=3",
20
		"GIT_CONFIG_KEY_0=safe.directory",
21
		"GIT_CONFIG_VALUE_0=*",
22
		"GIT_CONFIG_KEY_1=pack.threads",
23
		"GIT_CONFIG_VALUE_1=1",
24
		"GIT_CONFIG_KEY_2=core.deltaBaseCacheLimit",
25
		"GIT_CONFIG_VALUE_2=" + gitHTTPDeltaBaseCacheLimit,
26
	}
27
}
28
29
// handleGitHTTP serves Git smart HTTP protocol requests by proxying to git-http-backend.
30
// This enables `git clone` over HTTPS.
31
func (s *server) handleGitHTTP(w http.ResponseWriter, r *http.Request, repo *RepoInfo) {
32
	select {
33
	case s.gitHTTPSlots <- struct{}{}:
34
		defer func() { <-s.gitHTTPSlots }()
35
	default:
36
		w.Header().Set("Retry-After", "1")
37
		http.Error(w, "too many concurrent Git operations", http.StatusServiceUnavailable)
38
		return
39
	}
40
	gitHTTPBackend, err := exec.LookPath("git-http-backend")
41
	if err != nil {
42
		gitExecPath, err2 := exec.Command("git", "--exec-path").Output()
43
		if err2 != nil {
44
			http.Error(w, "git-http-backend not found", http.StatusInternalServerError)
45
			return
46
		}
47
		gitHTTPBackend = strings.TrimSpace(string(gitExecPath)) + "/git-http-backend"
48
	}
49
50
	// Strip the base URL prefix and repo name to get the path info that git-http-backend expects.
51
	// e.g. /baseurl/repo/info/refs -> /repo.git/info/refs
52
	pathInfo := r.URL.Path
53
	if s.baseURL != "" {
54
		pathInfo = strings.TrimPrefix(pathInfo, s.baseURL)
55
	}
56
	// Replace /<reponame>/ with /<reponame>.git/
57
	pathInfo = strings.Replace(pathInfo, "/"+repo.Name+"/", "/"+repo.Name+".git/", 1)
58
59
	env := gitHTTPEnv()
60
	env[0] += s.scanPath
61
	handler := &cgi.Handler{
62
		Path:       gitHTTPBackend,
63
		Env:        env,
64
		InheritEnv: []string{"PATH"},
65
	}
66
67
	// Override PATH_INFO for the CGI handler.
68
	r2 := r.Clone(r.Context())
69
	r2.URL.Path = pathInfo
70
71
	handler.ServeHTTP(w, r2)
72
}
73
74
// isGitHTTPRequest returns true if the request is a Git smart HTTP protocol request.
75
func isGitHTTPRequest(r *http.Request) bool {
76
	query := r.URL.RawQuery
77
78
	if strings.HasSuffix(r.URL.Path, "/info/refs") &&
79
		(strings.Contains(query, "service=git-upload-pack") || strings.Contains(query, "service=git-receive-pack")) {
80
		return true
81
	}
82
	if strings.HasSuffix(r.URL.Path, "/git-upload-pack") || strings.HasSuffix(r.URL.Path, "/git-receive-pack") {
83
		return true
84
	}
85
	if strings.HasSuffix(r.URL.Path, "/HEAD") || strings.Contains(r.URL.Path, "/objects/") {
86
		return true
87
	}
88
	return false
89
}