A vibe coded tangled fork which supports pijul.
1package state
2
3import (
4 "fmt"
5 "io"
6 "net/http"
7
8 "github.com/bluesky-social/indigo/atproto/identity"
9 "github.com/go-chi/chi/v5"
10 "tangled.org/core/appview/models"
11)
12
13// allowedResponseHeaders is the set of headers we will forward from the knot
14// back to the client. everything else is stripped.
15var allowedResponseHeaders = map[string]bool{
16 "Content-Encoding": true,
17 "Transfer-Encoding": true,
18 "Cache-Control": true,
19 "Expires": true,
20 "Pragma": true,
21}
22
23func copyAllowedHeaders(dst, src http.Header) {
24 for k, vv := range src {
25 if allowedResponseHeaders[http.CanonicalHeaderKey(k)] {
26 for _, v := range vv {
27 dst.Add(k, v)
28 }
29 }
30 }
31}
32
33func setGitHeaders(w http.ResponseWriter, contentType string) {
34 w.Header().Set("Content-Type", contentType)
35 w.Header().Set("Content-Disposition", "attachment")
36 w.Header().Set("X-Content-Type-Options", "nosniff")
37}
38
39func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) {
40 user := r.Context().Value("resolvedId").(identity.Identity)
41 repo := r.Context().Value("repo").(*models.Repo)
42
43 scheme := "https"
44 if s.config.Core.Dev {
45 scheme = "http"
46 }
47
48 // check for the 'service' url param
49 service := r.URL.Query().Get("service")
50 var contentType string
51 switch service {
52 case "git-receive-pack":
53 contentType = "application/x-git-receive-pack-advertisement"
54 default:
55 // git-upload-pack is the default service for git-clone / git-fetch.
56 contentType = "application/x-git-upload-pack-advertisement"
57 }
58
59 targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
60 s.proxyRequest(w, r, targetURL, contentType)
61}
62
63func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) {
64 user, ok := r.Context().Value("resolvedId").(identity.Identity)
65 if !ok {
66 http.Error(w, "failed to resolve user", http.StatusInternalServerError)
67 return
68 }
69 repo := r.Context().Value("repo").(*models.Repo)
70
71 scheme := "https"
72 if s.config.Core.Dev {
73 scheme = "http"
74 }
75
76 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
77 s.proxyRequest(w, r, targetURL, "application/x-git-upload-archive-result")
78}
79
80func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) {
81 user, ok := r.Context().Value("resolvedId").(identity.Identity)
82 if !ok {
83 http.Error(w, "failed to resolve user", http.StatusInternalServerError)
84 return
85 }
86 repo := r.Context().Value("repo").(*models.Repo)
87
88 scheme := "https"
89 if s.config.Core.Dev {
90 scheme = "http"
91 }
92
93 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
94 s.proxyRequest(w, r, targetURL, "application/x-git-upload-pack-result")
95}
96
97func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) {
98 user, ok := r.Context().Value("resolvedId").(identity.Identity)
99 if !ok {
100 http.Error(w, "failed to resolve user", http.StatusInternalServerError)
101 return
102 }
103 repo := r.Context().Value("repo").(*models.Repo)
104
105 scheme := "https"
106 if s.config.Core.Dev {
107 scheme = "http"
108 }
109
110 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
111 s.proxyRequest(w, r, targetURL, "application/x-git-receive-pack-result")
112}
113
114func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string, contentType string) {
115 client := &http.Client{}
116
117 proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
118 if err != nil {
119 http.Error(w, err.Error(), http.StatusInternalServerError)
120 return
121 }
122
123 proxyReq.Header = r.Header.Clone()
124
125 repoOwnerHandle := chi.URLParam(r, "user")
126 proxyReq.Header.Set("x-tangled-repo-owner-handle", repoOwnerHandle)
127
128 resp, err := client.Do(proxyReq)
129 if err != nil {
130 http.Error(w, err.Error(), http.StatusInternalServerError)
131 return
132 }
133 defer resp.Body.Close()
134
135 // selectively copy only allowed headers
136 copyAllowedHeaders(w.Header(), resp.Header)
137
138 setGitHeaders(w, contentType)
139
140 w.WriteHeader(resp.StatusCode)
141
142 if _, err := io.Copy(w, resp.Body); err != nil {
143 http.Error(w, err.Error(), http.StatusInternalServerError)
144 return
145 }
146}