git-http.go 3.12 KB
Newer Older
Jacob Vosmaer's avatar
Jacob Vosmaer committed
1 2 3 4
/*
In this file we handle the Git 'smart HTTP' protocol
*/

5
package git
Jacob Vosmaer's avatar
Jacob Vosmaer committed
6 7 8 9

import (
	"fmt"
	"io"
10
	"log"
Jacob Vosmaer's avatar
Jacob Vosmaer committed
11
	"net/http"
12
	"os"
13
	"os/exec"
14
	"path"
Kamil Trzcinski's avatar
Kamil Trzcinski committed
15
	"path/filepath"
16
	"strings"
17
	"sync"
Jacob Vosmaer's avatar
Jacob Vosmaer committed
18 19 20

	"gitlab.com/gitlab-org/gitlab-workhorse/internal/api"
	"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
Jacob Vosmaer's avatar
Jacob Vosmaer committed
21 22
)

Jacob Vosmaer's avatar
Jacob Vosmaer committed
23 24 25 26 27 28 29 30
func ReceivePack(a *api.API) http.Handler {
	return postRPCHandler(a, "handleReceivePack", handleReceivePack)
}

func UploadPack(a *api.API) http.Handler {
	return postRPCHandler(a, "handleUploadPack", handleUploadPack)
}

31
func postRPCHandler(a *api.API, name string, handler func(*GitHttpResponseWriter, *http.Request, *api.Response) error) http.Handler {
Jacob Vosmaer's avatar
Jacob Vosmaer committed
32
	return repoPreAuthorizeHandler(a, func(rw http.ResponseWriter, r *http.Request, ar *api.Response) {
33 34
		cr := &countReadCloser{ReadCloser: r.Body}
		r.Body = cr
Jacob Vosmaer's avatar
Jacob Vosmaer committed
35 36 37

		w := NewGitHttpResponseWriter(rw)
		defer func() {
38
			w.Log(r, cr.Count())
Jacob Vosmaer's avatar
Jacob Vosmaer committed
39 40
		}()

41
		if err := handler(w, r, ar); err != nil {
Jacob Vosmaer's avatar
Jacob Vosmaer committed
42
			// If the handler already wrote a response this WriteHeader call is a
43 44
			// no-op. It never reaches net/http because GitHttpResponseWriter calls
			// WriteHeader on its underlying ResponseWriter at most once.
45 46
			w.WriteHeader(500)
			helper.LogError(r, fmt.Errorf("%s: %v", name, err))
Jacob Vosmaer's avatar
Jacob Vosmaer committed
47 48
		}
	})
49 50
}

51 52 53 54 55 56 57 58 59 60
func looksLikeRepo(p string) bool {
	// If /path/to/foo.git/objects exists then let's assume it is a valid Git
	// repository.
	if _, err := os.Stat(path.Join(p, "objects")); err != nil {
		log.Print(err)
		return false
	}
	return true
}

61
func repoPreAuthorizeHandler(myAPI *api.API, handleFunc api.HandleFunc) http.Handler {
62
	return myAPI.PreAuthorizeHandler(func(w http.ResponseWriter, r *http.Request, a *api.Response) {
Jacob Vosmaer's avatar
Jacob Vosmaer committed
63
		if a.RepoPath == "" {
64
			helper.Fail500(w, r, fmt.Errorf("repoPreAuthorizeHandler: RepoPath empty"))
65 66 67
			return
		}

Jacob Vosmaer's avatar
Jacob Vosmaer committed
68
		if !looksLikeRepo(a.RepoPath) {
69 70 71 72
			http.Error(w, "Not Found", 404)
			return
		}

Jacob Vosmaer's avatar
Jacob Vosmaer committed
73
		handleFunc(w, r, a)
74 75 76
	}, "")
}

77
func startGitCommand(a *api.Response, stdin io.Reader, stdout io.Writer, action string, options ...string) (cmd *exec.Cmd, err error) {
78
	// Prepare our Git subprocess
79 80 81 82
	args := []string{subCommand(action), "--stateless-rpc"}
	args = append(args, options...)
	args = append(args, a.RepoPath)
	cmd = gitCommand(a.GL_ID, "git", args...)
83 84
	cmd.Stdin = stdin
	cmd.Stdout = stdout
85 86

	if err = cmd.Start(); err != nil {
87
		return nil, fmt.Errorf("start %v: %v", cmd.Args, err)
88 89
	}

90
	return cmd, nil
Jacob Vosmaer's avatar
Jacob Vosmaer committed
91 92
}

93 94 95 96 97
func writePostRPCHeader(w http.ResponseWriter, action string) {
	w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-result", action))
	w.Header().Set("Cache-Control", "no-cache")
}

98 99 100 101 102 103 104
func getService(r *http.Request) string {
	if r.Method == "GET" {
		return r.URL.Query().Get("service")
	}
	return filepath.Base(r.URL.Path)
}

105 106 107
func isExitError(err error) bool {
	_, ok := err.(*exec.ExitError)
	return ok
Jacob Vosmaer's avatar
Jacob Vosmaer committed
108 109
}

110 111
func subCommand(rpc string) string {
	return strings.TrimPrefix(rpc, "git-")
Jacob Vosmaer's avatar
Jacob Vosmaer committed
112
}
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134

type countReadCloser struct {
	n int64
	io.ReadCloser
	sync.Mutex
}

func (c *countReadCloser) Read(p []byte) (n int, err error) {
	n, err = c.ReadCloser.Read(p)

	c.Lock()
	defer c.Unlock()
	c.n += int64(n)

	return n, err
}

func (c *countReadCloser) Count() int64 {
	c.Lock()
	defer c.Unlock()
	return c.n
}