blob: 38a8b548530d8dd7c133c477a3b974ab62dda6bf [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package dockerimg
2
3import (
Philip Zeyliger5e227dd2025-04-21 15:55:29 -07004 "crypto/subtle"
Earl Lee2e463fb2025-04-17 11:22:22 -07005 "fmt"
6 "log/slog"
7 "net/http"
8 "net/http/cgi"
9 "os/exec"
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070010 "runtime"
Earl Lee2e463fb2025-04-17 11:22:22 -070011 "strings"
12)
13
14type gitHTTP struct {
15 gitRepoRoot string
Josh Bleecher Snyder9f6a9982025-04-22 17:34:15 -070016 pass []byte
Earl Lee2e463fb2025-04-17 11:22:22 -070017}
18
19func (g *gitHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
20 defer func() {
21 if err := recover(); err != nil {
22 slog.ErrorContext(r.Context(), "gitHTTP.ServeHTTP panic", slog.Any("recovered_err", err))
23
24 // Return an error response to the client
25 http.Error(w, fmt.Sprintf("panic: %v\n", err), http.StatusInternalServerError)
26 }
27 }()
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070028
29 // Get the Authorization header
30 username, password, ok := r.BasicAuth()
31
32 // Check if credentials were provided
33 if !ok {
34 // No credentials provided, return 401 Unauthorized
35 w.Header().Set("WWW-Authenticate", `Basic realm="Sketch Git Repository"`)
36 http.Error(w, "Unauthorized", http.StatusUnauthorized)
37 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
Earl Lee2e463fb2025-04-17 11:22:22 -070038 return
39 }
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070040
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070041 // Check if credentials are valid
Josh Bleecher Snyder9f6a9982025-04-22 17:34:15 -070042 if username != "sketch" || subtle.ConstantTimeCompare([]byte(password), g.pass) != 1 {
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070043 w.Header().Set("WWW-Authenticate", `Basic realm="Git Repository"`)
44 http.Error(w, "Unauthorized", http.StatusUnauthorized)
45 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
46 return
47 }
48
49 if runtime.GOOS == "darwin" {
50 // On the Mac, Docker connections show up from localhost. On Linux, the docker
51 // network is more arbitrary, so we don't do this additional check there.
52 if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
53 slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
54 http.Error(w, "no", http.StatusUnauthorized)
55 return
56 }
57 }
Earl Lee2e463fb2025-04-17 11:22:22 -070058 gitBin, err := exec.LookPath("git")
59 if err != nil {
60 http.Error(w, "no git: "+err.Error(), http.StatusInternalServerError)
61 return
62 }
63
64 w.Header().Set("Cache-Control", "no-cache")
65 h := &cgi.Handler{
66 Path: gitBin,
67 Args: []string{"http-backend"},
68 Dir: g.gitRepoRoot,
69 Env: []string{
70 "GIT_PROJECT_ROOT=" + g.gitRepoRoot,
71 "PATH_INFO=" + r.URL.Path,
72 "QUERY_STRING=" + r.URL.RawQuery,
73 "REQUEST_METHOD=" + r.Method,
74 "GIT_HTTP_EXPORT_ALL=true",
75 "GIT_HTTP_ALLOW_REPACK=true",
76 "GIT_HTTP_ALLOW_PUSH=true",
77 "GIT_HTTP_VERBOSE=1",
78 },
79 }
80 h.ServeHTTP(w, r)
81}