blob: 62708389c64cb40163ba5fd04a0d506ac4ee1ea5 [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"
Josh Bleecher Snyder3e2111b2025-04-30 17:53:28 +00006 "io"
Earl Lee2e463fb2025-04-17 11:22:22 -07007 "log/slog"
8 "net/http"
9 "net/http/cgi"
10 "os/exec"
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070011 "runtime"
Earl Lee2e463fb2025-04-17 11:22:22 -070012 "strings"
13)
14
15type gitHTTP struct {
16 gitRepoRoot string
Josh Bleecher Snyder9f6a9982025-04-22 17:34:15 -070017 pass []byte
Josh Bleecher Snyder3e2111b2025-04-30 17:53:28 +000018 browserC chan string // browser launch requests
Earl Lee2e463fb2025-04-17 11:22:22 -070019}
20
21func (g *gitHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
22 defer func() {
23 if err := recover(); err != nil {
24 slog.ErrorContext(r.Context(), "gitHTTP.ServeHTTP panic", slog.Any("recovered_err", err))
25
26 // Return an error response to the client
27 http.Error(w, fmt.Sprintf("panic: %v\n", err), http.StatusInternalServerError)
28 }
29 }()
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070030
31 // Get the Authorization header
32 username, password, ok := r.BasicAuth()
33
34 // Check if credentials were provided
35 if !ok {
36 // No credentials provided, return 401 Unauthorized
37 w.Header().Set("WWW-Authenticate", `Basic realm="Sketch Git Repository"`)
38 http.Error(w, "Unauthorized", http.StatusUnauthorized)
39 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
Earl Lee2e463fb2025-04-17 11:22:22 -070040 return
41 }
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070042
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070043 // Check if credentials are valid
Josh Bleecher Snyder9f6a9982025-04-22 17:34:15 -070044 if username != "sketch" || subtle.ConstantTimeCompare([]byte(password), g.pass) != 1 {
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070045 w.Header().Set("WWW-Authenticate", `Basic realm="Git Repository"`)
46 http.Error(w, "Unauthorized", http.StatusUnauthorized)
47 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
48 return
49 }
50
Josh Bleecher Snyder3e2111b2025-04-30 17:53:28 +000051 // TODO: real mux?
52 if strings.HasPrefix(r.URL.Path, "/browser") {
53 if r.Method != http.MethodPost {
54 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
55 return
56 }
57 body, err := io.ReadAll(r.Body)
58 if err != nil {
59 http.Error(w, "Failed to read request body: "+err.Error(), http.StatusBadRequest)
60 return
61 }
62 defer r.Body.Close()
63 url := strings.TrimSpace(string(body))
64 if len(url) == 0 {
65 http.Error(w, "URL cannot be empty", http.StatusBadRequest)
66 return
67 }
68 select {
69 case g.browserC <- string(url):
70 slog.InfoContext(r.Context(), "open browser", "url", url)
71 w.WriteHeader(http.StatusOK)
72 default:
73 http.Error(w, "Too many browser launch requests", http.StatusTooManyRequests)
74 }
75 return
76 }
77
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070078 if runtime.GOOS == "darwin" {
79 // On the Mac, Docker connections show up from localhost. On Linux, the docker
80 // network is more arbitrary, so we don't do this additional check there.
81 if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
82 slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
83 http.Error(w, "no", http.StatusUnauthorized)
84 return
85 }
86 }
Earl Lee2e463fb2025-04-17 11:22:22 -070087 gitBin, err := exec.LookPath("git")
88 if err != nil {
89 http.Error(w, "no git: "+err.Error(), http.StatusInternalServerError)
90 return
91 }
92
93 w.Header().Set("Cache-Control", "no-cache")
94 h := &cgi.Handler{
95 Path: gitBin,
96 Args: []string{"http-backend"},
97 Dir: g.gitRepoRoot,
98 Env: []string{
99 "GIT_PROJECT_ROOT=" + g.gitRepoRoot,
100 "PATH_INFO=" + r.URL.Path,
101 "QUERY_STRING=" + r.URL.RawQuery,
102 "REQUEST_METHOD=" + r.Method,
103 "GIT_HTTP_EXPORT_ALL=true",
104 "GIT_HTTP_ALLOW_REPACK=true",
105 "GIT_HTTP_ALLOW_PUSH=true",
106 "GIT_HTTP_VERBOSE=1",
107 },
108 }
109 h.ServeHTTP(w, r)
110}