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