forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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 Dir: repoPath,
35 Stdout: w,
36 }
37
38 serviceName := r.URL.Query().Get("service")
39 switch serviceName {
40 case "git-upload-pack":
41 w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
42 w.WriteHeader(http.StatusOK)
43
44 if err := cmd.InfoRefs(); err != nil {
45 gitError(w, err.Error(), http.StatusInternalServerError)
46 d.l.Error("git: process failed", "handler", "InfoRefs", "service", serviceName, "error", err)
47 return
48 }
49 case "git-receive-pack":
50 d.RejectPush(w, r, name)
51 default:
52 gitError(w, fmt.Sprintf("service unsupported: '%s'", serviceName), http.StatusForbidden)
53 }
54}
55
56func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
57 did := chi.URLParam(r, "did")
58 name := chi.URLParam(r, "name")
59 repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
60 if err != nil {
61 writeError(w, err.Error(), 500)
62 d.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err)
63 return
64 }
65
66 var bodyReader io.ReadCloser = r.Body
67 if r.Header.Get("Content-Encoding") == "gzip" {
68 gzipReader, err := gzip.NewReader(r.Body)
69 if err != nil {
70 writeError(w, err.Error(), 500)
71 d.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err)
72 return
73 }
74 defer gzipReader.Close()
75 bodyReader = gzipReader
76 }
77
78 w.Header().Set("Content-Type", "application/x-git-upload-pack-result")
79 w.Header().Set("Connection", "Keep-Alive")
80
81 d.l.Info("git: executing git-upload-pack", "handler", "UploadPack", "repo", repo)
82
83 cmd := service.ServiceCommand{
84 Dir: repo,
85 Stdout: w,
86 Stdin: bodyReader,
87 }
88
89 w.WriteHeader(http.StatusOK)
90
91 if err := cmd.UploadPack(); err != nil {
92 d.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err)
93 return
94 }
95}
96
97func (d *Handle) ReceivePack(w http.ResponseWriter, r *http.Request) {
98 did := chi.URLParam(r, "did")
99 name := chi.URLParam(r, "name")
100 _, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
101 if err != nil {
102 gitError(w, err.Error(), http.StatusForbidden)
103 d.l.Error("git: failed to secure join repo path", "handler", "ReceivePack", "error", err)
104 return
105 }
106
107 d.RejectPush(w, r, name)
108}
109
110func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
111 // A text/plain response will cause git to print each line of the body
112 // prefixed with "remote: ".
113 w.Header().Set("content-type", "text/plain; charset=UTF-8")
114 w.WriteHeader(http.StatusForbidden)
115
116 fmt.Fprintf(w, "Pushes are only supported over SSH.")
117
118 // If the appview gave us the repository owner's handle we can attempt to
119 // construct the correct ssh url.
120 ownerHandle := r.Header.Get("x-tangled-repo-owner-handle")
121 if ownerHandle != "" && !strings.ContainsAny(ownerHandle, ":") {
122 hostname := d.c.Server.Hostname
123 if strings.Contains(hostname, ":") {
124 hostname = strings.Split(hostname, ":")[0]
125 }
126
127 fmt.Fprintf(w, " Try:\ngit remote set-url --push origin git@%s:%s/%s\n\n... and push again.", hostname, ownerHandle, unqualifiedRepoName)
128 }
129 fmt.Fprintf(w, "\n\n")
130}
131
132func gitError(w http.ResponseWriter, msg string, status int) {
133 w.Header().Set("content-type", "text/plain; charset=UTF-8")
134 w.WriteHeader(status)
135 fmt.Fprintf(w, "%s\n", msg)
136}