this repo has no description
1package knotserver
2
3import (
4 "compress/gzip"
5 "fmt"
6 "io"
7 "net/http"
8 "path/filepath"
9 "strings"
10
11 securejoin "github.com/cyphar/filepath-securejoin"
12 "github.com/go-chi/chi/v5"
13 "tangled.sh/tangled.sh/core/knotserver/git/service"
14)
15
16func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) {
17 did := chi.URLParam(r, "did")
18 name := chi.URLParam(r, "name")
19 repoName, err := securejoin.SecureJoin(did, name)
20 if err != nil {
21 gitError(w, "repository not found", http.StatusNotFound)
22 d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
23 return
24 }
25
26 repoPath, err := securejoin.SecureJoin(d.c.Repo.ScanPath, repoName)
27 if err != nil {
28 gitError(w, "repository not found", http.StatusNotFound)
29 d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
30 return
31 }
32
33 cmd := service.ServiceCommand{
34 GitProtocol: r.Header.Get("Git-Protocol"),
35 Dir: repoPath,
36 Stdout: w,
37 }
38
39 serviceName := r.URL.Query().Get("service")
40 switch serviceName {
41 case "git-upload-pack":
42 w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
43 w.WriteHeader(http.StatusOK)
44
45 if err := cmd.InfoRefs(); err != nil {
46 gitError(w, err.Error(), http.StatusInternalServerError)
47 d.l.Error("git: process failed", "handler", "InfoRefs", "service", serviceName, "error", err)
48 return
49 }
50 case "git-receive-pack":
51 d.RejectPush(w, r, name)
52 default:
53 gitError(w, fmt.Sprintf("service unsupported: '%s'", serviceName), http.StatusForbidden)
54 }
55}
56
57func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
58 did := chi.URLParam(r, "did")
59 name := chi.URLParam(r, "name")
60 repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
61 if err != nil {
62 writeError(w, err.Error(), 500)
63 d.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err)
64 return
65 }
66
67 var bodyReader io.ReadCloser = r.Body
68 if r.Header.Get("Content-Encoding") == "gzip" {
69 gzipReader, err := gzip.NewReader(r.Body)
70 if err != nil {
71 writeError(w, err.Error(), 500)
72 d.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err)
73 return
74 }
75 defer gzipReader.Close()
76 bodyReader = gzipReader
77 }
78
79 w.Header().Set("Content-Type", "application/x-git-upload-pack-result")
80 w.Header().Set("Connection", "Keep-Alive")
81
82 d.l.Info("git: executing git-upload-pack", "handler", "UploadPack", "repo", repo)
83
84 cmd := service.ServiceCommand{
85 GitProtocol: r.Header.Get("Git-Protocol"),
86 Dir: repo,
87 Stdout: w,
88 Stdin: bodyReader,
89 }
90
91 w.WriteHeader(http.StatusOK)
92
93 if err := cmd.UploadPack(); err != nil {
94 d.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err)
95 return
96 }
97}
98
99func (d *Handle) ReceivePack(w http.ResponseWriter, r *http.Request) {
100 did := chi.URLParam(r, "did")
101 name := chi.URLParam(r, "name")
102 _, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
103 if err != nil {
104 gitError(w, err.Error(), http.StatusForbidden)
105 d.l.Error("git: failed to secure join repo path", "handler", "ReceivePack", "error", err)
106 return
107 }
108
109 d.RejectPush(w, r, name)
110}
111
112func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
113 // A text/plain response will cause git to print each line of the body
114 // prefixed with "remote: ".
115 w.Header().Set("content-type", "text/plain; charset=UTF-8")
116 w.WriteHeader(http.StatusForbidden)
117
118 fmt.Fprintf(w, "Pushes are only supported over SSH.")
119
120 // If the appview gave us the repository owner's handle we can attempt to
121 // construct the correct ssh url.
122 ownerHandle := r.Header.Get("x-tangled-repo-owner-handle")
123 if ownerHandle != "" && !strings.ContainsAny(ownerHandle, ":") {
124 hostname := d.c.Server.Hostname
125 if strings.Contains(hostname, ":") {
126 hostname = strings.Split(hostname, ":")[0]
127 }
128
129 fmt.Fprintf(w, " Try:\ngit remote set-url --push origin git@%s:%s/%s\n\n... and push again.", hostname, ownerHandle, unqualifiedRepoName)
130 }
131 fmt.Fprintf(w, "\n\n")
132}
133
134func gitError(w http.ResponseWriter, msg string, status int) {
135 w.Header().Set("content-type", "text/plain; charset=UTF-8")
136 w.WriteHeader(status)
137 fmt.Fprintf(w, "%s\n", msg)
138}