18947: Refactor arv-git-httpd as an arvados-server subcommand.
[arvados.git] / services / githttpd / git_handler.go
diff --git a/services/githttpd/git_handler.go b/services/githttpd/git_handler.go
new file mode 100644 (file)
index 0000000..7c94294
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package githttpd
+
+import (
+       "context"
+       "net"
+       "net/http"
+       "net/http/cgi"
+       "os"
+
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+)
+
+// gitHandler is an http.Handler that invokes git-http-backend (or
+// whatever backend is configured) via CGI, with appropriate
+// environment variables in place for git-http-backend or
+// gitolite-shell.
+type gitHandler struct {
+       cgi.Handler
+}
+
+func newGitHandler(ctx context.Context, cluster *arvados.Cluster) http.Handler {
+       const glBypass = "GL_BYPASS_ACCESS_CHECKS"
+       const glHome = "GITOLITE_HTTP_HOME"
+       var env []string
+       path := os.Getenv("PATH")
+       if cluster.Git.GitoliteHome != "" {
+               env = append(env,
+                       glHome+"="+cluster.Git.GitoliteHome,
+                       glBypass+"=1")
+               path = path + ":" + cluster.Git.GitoliteHome + "/bin"
+       } else if home, bypass := os.Getenv(glHome), os.Getenv(glBypass); home != "" || bypass != "" {
+               env = append(env, glHome+"="+home, glBypass+"="+bypass)
+               ctxlog.FromContext(ctx).Printf("DEPRECATED: Passing through %s and %s environment variables. Use GitoliteHome configuration instead.", glHome, glBypass)
+       }
+
+       var listen arvados.URL
+       for listen = range cluster.Services.GitHTTP.InternalURLs {
+               break
+       }
+       env = append(env,
+               "GIT_PROJECT_ROOT="+cluster.Git.Repositories,
+               "GIT_HTTP_EXPORT_ALL=",
+               "SERVER_ADDR="+listen.Host,
+               "PATH="+path)
+       return &gitHandler{
+               Handler: cgi.Handler{
+                       Path: cluster.Git.GitCommand,
+                       Dir:  cluster.Git.Repositories,
+                       Env:  env,
+                       Args: []string{"http-backend"},
+               },
+       }
+}
+
+func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr)
+       if err != nil {
+               ctxlog.FromContext(r.Context()).Errorf("Internal error: SplitHostPort(r.RemoteAddr==%q): %s", r.RemoteAddr, err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
+       }
+
+       // Copy the wrapped cgi.Handler, so these request-specific
+       // variables don't leak into the next request.
+       handlerCopy := h.Handler
+       handlerCopy.Env = append(handlerCopy.Env,
+               // In Go1.5 we can skip this, net/http/cgi will do it for us:
+               "REMOTE_HOST="+remoteHost,
+               "REMOTE_ADDR="+remoteHost,
+               "REMOTE_PORT="+remotePort,
+               // Ideally this would be a real username:
+               "REMOTE_USER="+r.RemoteAddr,
+       )
+       handlerCopy.ServeHTTP(w, r)
+}