Merge branch '18947-githttpd'
[arvados.git] / services / githttpd / git_handler.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package githttpd
6
7 import (
8         "context"
9         "net"
10         "net/http"
11         "net/http/cgi"
12         "os"
13
14         "git.arvados.org/arvados.git/sdk/go/arvados"
15         "git.arvados.org/arvados.git/sdk/go/ctxlog"
16 )
17
18 // gitHandler is an http.Handler that invokes git-http-backend (or
19 // whatever backend is configured) via CGI, with appropriate
20 // environment variables in place for git-http-backend or
21 // gitolite-shell.
22 type gitHandler struct {
23         cgi.Handler
24 }
25
26 func newGitHandler(ctx context.Context, cluster *arvados.Cluster) http.Handler {
27         const glBypass = "GL_BYPASS_ACCESS_CHECKS"
28         const glHome = "GITOLITE_HTTP_HOME"
29         var env []string
30         path := os.Getenv("PATH")
31         if cluster.Git.GitoliteHome != "" {
32                 env = append(env,
33                         glHome+"="+cluster.Git.GitoliteHome,
34                         glBypass+"=1")
35                 path = path + ":" + cluster.Git.GitoliteHome + "/bin"
36         } else if home, bypass := os.Getenv(glHome), os.Getenv(glBypass); home != "" || bypass != "" {
37                 env = append(env, glHome+"="+home, glBypass+"="+bypass)
38                 ctxlog.FromContext(ctx).Printf("DEPRECATED: Passing through %s and %s environment variables. Use GitoliteHome configuration instead.", glHome, glBypass)
39         }
40
41         var listen arvados.URL
42         for listen = range cluster.Services.GitHTTP.InternalURLs {
43                 break
44         }
45         env = append(env,
46                 "GIT_PROJECT_ROOT="+cluster.Git.Repositories,
47                 "GIT_HTTP_EXPORT_ALL=",
48                 "SERVER_ADDR="+listen.Host,
49                 "PATH="+path)
50         return &gitHandler{
51                 Handler: cgi.Handler{
52                         Path: cluster.Git.GitCommand,
53                         Dir:  cluster.Git.Repositories,
54                         Env:  env,
55                         Args: []string{"http-backend"},
56                 },
57         }
58 }
59
60 func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
61         remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr)
62         if err != nil {
63                 ctxlog.FromContext(r.Context()).Errorf("Internal error: SplitHostPort(r.RemoteAddr==%q): %s", r.RemoteAddr, err)
64                 w.WriteHeader(http.StatusInternalServerError)
65                 return
66         }
67
68         // Copy the wrapped cgi.Handler, so these request-specific
69         // variables don't leak into the next request.
70         handlerCopy := h.Handler
71         handlerCopy.Env = append(handlerCopy.Env,
72                 // In Go1.5 we can skip this, net/http/cgi will do it for us:
73                 "REMOTE_HOST="+remoteHost,
74                 "REMOTE_ADDR="+remoteHost,
75                 "REMOTE_PORT="+remotePort,
76                 // Ideally this would be a real username:
77                 "REMOTE_USER="+r.RemoteAddr,
78         )
79         handlerCopy.ServeHTTP(w, r)
80 }