this repo has no description
1package knotserver
2
3import (
4 "compress/gzip"
5 "errors"
6 "fmt"
7 "html/template"
8 "log"
9 "net/http"
10 "path/filepath"
11 "strconv"
12 "strings"
13
14 "github.com/go-chi/chi/v5"
15 "github.com/go-git/go-git/v5/plumbing"
16 "github.com/icyphox/bild/git"
17 "github.com/russross/blackfriday/v2"
18)
19
20func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
21 w.Write([]byte("This is a knot, part of the wider Tangle network: https://knots.sh"))
22}
23
24func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
25 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
26
27 gr, err := git.Open(path, "")
28 if err != nil {
29 if errors.Is(err, plumbing.ErrReferenceNotFound) {
30 writeMsg(w, "repo empty")
31 return
32 } else {
33 log.Println(err)
34 notFound(w)
35 return
36 }
37 }
38 commits, err := gr.Commits()
39 if err != nil {
40 writeError(w, err.Error(), http.StatusInternalServerError)
41 log.Println(err)
42 return
43 }
44
45 var readmeContent template.HTML
46 for _, readme := range h.c.Repo.Readme {
47 ext := filepath.Ext(readme)
48 content, _ := gr.FileContent(readme)
49 if len(content) > 0 {
50 switch ext {
51 case ".md", ".mkd", ".markdown":
52 unsafe := blackfriday.Run(
53 []byte(content),
54 blackfriday.WithExtensions(blackfriday.CommonExtensions),
55 )
56 html := sanitize(unsafe)
57 readmeContent = template.HTML(html)
58 default:
59 safe := sanitize([]byte(content))
60 readmeContent = template.HTML(
61 fmt.Sprintf(`<pre>%s</pre>`, safe),
62 )
63 }
64 break
65 }
66 }
67
68 if readmeContent == "" {
69 log.Printf("no readme found for %s", path)
70 }
71
72 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
73 if err != nil {
74 writeError(w, err.Error(), http.StatusInternalServerError)
75 log.Println(err)
76 return
77 }
78
79 if len(commits) >= 3 {
80 commits = commits[:3]
81 }
82 data := make(map[string]any)
83 data["ref"] = mainBranch
84 data["readme"] = readmeContent
85 data["commits"] = commits
86 data["desc"] = getDescription(path)
87 data["servername"] = h.c.Server.Name
88 data["meta"] = h.c.Meta
89
90 writeJSON(w, data)
91 return
92}
93
94func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
95 treePath := chi.URLParam(r, "*")
96 ref := chi.URLParam(r, "ref")
97
98 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
99 gr, err := git.Open(path, ref)
100 if err != nil {
101 notFound(w)
102 return
103 }
104
105 files, err := gr.FileTree(treePath)
106 if err != nil {
107 writeError(w, err.Error(), http.StatusInternalServerError)
108 log.Println(err)
109 return
110 }
111
112 data := make(map[string]any)
113 data["ref"] = ref
114 data["parent"] = treePath
115 data["desc"] = getDescription(path)
116 data["dotdot"] = filepath.Dir(treePath)
117
118 h.listFiles(files, data, w)
119 return
120}
121
122func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
123 var raw bool
124 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
125 raw = rawParam
126 }
127
128 treePath := chi.URLParam(r, "*")
129 ref := chi.URLParam(r, "ref")
130
131 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
132 gr, err := git.Open(path, ref)
133 if err != nil {
134 notFound(w)
135 return
136 }
137
138 contents, err := gr.FileContent(treePath)
139 if err != nil {
140 writeError(w, err.Error(), http.StatusInternalServerError)
141 return
142 }
143 data := make(map[string]any)
144 data["ref"] = ref
145 data["desc"] = getDescription(path)
146 data["path"] = treePath
147
148 safe := sanitize([]byte(contents))
149
150 if raw {
151 h.showRaw(string(safe), w)
152 } else {
153 h.showFile(string(safe), data, w)
154 }
155}
156
157func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
158 name := displayRepoName(r)
159
160 file := chi.URLParam(r, "file")
161
162 // TODO: extend this to add more files compression (e.g.: xz)
163 if !strings.HasSuffix(file, ".tar.gz") {
164 notFound(w)
165 return
166 }
167
168 ref := strings.TrimSuffix(file, ".tar.gz")
169
170 // This allows the browser to use a proper name for the file when
171 // downloading
172 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
173 setContentDisposition(w, filename)
174 setGZipMIME(w)
175
176 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
177 gr, err := git.Open(path, ref)
178 if err != nil {
179 notFound(w)
180 return
181 }
182
183 gw := gzip.NewWriter(w)
184 defer gw.Close()
185
186 prefix := fmt.Sprintf("%s-%s", name, ref)
187 err = gr.WriteTar(gw, prefix)
188 if err != nil {
189 // once we start writing to the body we can't report error anymore
190 // so we are only left with printing the error.
191 log.Println(err)
192 return
193 }
194
195 err = gw.Flush()
196 if err != nil {
197 // once we start writing to the body we can't report error anymore
198 // so we are only left with printing the error.
199 log.Println(err)
200 return
201 }
202}
203
204func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
205 fmt.Println(r.URL.Path)
206 ref := chi.URLParam(r, "ref")
207
208 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
209 gr, err := git.Open(path, ref)
210 if err != nil {
211 notFound(w)
212 return
213 }
214
215 commits, err := gr.Commits()
216 if err != nil {
217 writeError(w, err.Error(), http.StatusInternalServerError)
218 log.Println(err)
219 return
220 }
221
222 data := make(map[string]interface{})
223 data["commits"] = commits
224 data["meta"] = h.c.Meta
225 data["ref"] = ref
226 data["desc"] = getDescription(path)
227 data["log"] = true
228
229 writeJSON(w, data)
230 return
231}
232
233func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
234 name := displayRepoName(r)
235 if h.isIgnored(name) {
236 notFound(w)
237 return
238 }
239 ref := chi.URLParam(r, "ref")
240
241 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
242 gr, err := git.Open(path, ref)
243 if err != nil {
244 notFound(w)
245 return
246 }
247
248 diff, err := gr.Diff()
249 if err != nil {
250 writeError(w, err.Error(), http.StatusInternalServerError)
251 log.Println(err)
252 return
253 }
254
255 data := make(map[string]interface{})
256
257 data["commit"] = diff.Commit
258 data["stat"] = diff.Stat
259 data["diff"] = diff.Diff
260 data["meta"] = h.c.Meta
261 data["ref"] = ref
262 data["desc"] = getDescription(path)
263
264 writeJSON(w, data)
265 return
266}
267
268func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
269 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
270 gr, err := git.Open(path, "")
271 if err != nil {
272 notFound(w)
273 return
274 }
275
276 tags, err := gr.Tags()
277 if err != nil {
278 // Non-fatal, we *should* have at least one branch to show.
279 log.Println(err)
280 }
281
282 branches, err := gr.Branches()
283 if err != nil {
284 log.Println(err)
285 writeError(w, err.Error(), http.StatusInternalServerError)
286 return
287 }
288
289 data := make(map[string]interface{})
290
291 data["meta"] = h.c.Meta
292 data["branches"] = branches
293 data["tags"] = tags
294 data["desc"] = getDescription(path)
295
296 writeJSON(w, data)
297 return
298}
299
300func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) {
301 f := chi.URLParam(r, "file")
302 f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f))
303
304 http.ServeFile(w, r, f)
305}
306
307// func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
308// session, _ := h.s.Get(r, "bild-session")
309// did := session.Values["did"].(string)
310
311// switch r.Method {
312// case http.MethodGet:
313// keys, err := h.db.GetPublicKeys(did)
314// if err != nil {
315// h.WriteOOBNotice(w, "keys", "Failed to list keys. Try again later.")
316// log.Println(err)
317// return
318// }
319
320// data := make(map[string]interface{})
321// data["keys"] = keys
322// if err := h.t.ExecuteTemplate(w, "settings/keys", data); err != nil {
323// log.Println(err)
324// return
325// }
326// case http.MethodPut:
327// key := r.FormValue("key")
328// name := r.FormValue("name")
329// client, _ := h.auth.AuthorizedClient(r)
330
331// _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
332// if err != nil {
333// h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.")
334// log.Printf("parsing public key: %s", err)
335// return
336// }
337
338// if err := h.db.AddPublicKey(did, name, key); err != nil {
339// h.WriteOOBNotice(w, "keys", "Failed to add key.")
340// log.Printf("adding public key: %s", err)
341// return
342// }
343
344// // store in pds too
345// resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
346// Collection: "sh.bild.publicKey",
347// Repo: did,
348// Rkey: uuid.New().String(),
349// Record: &lexutil.LexiconTypeDecoder{Val: &shbild.PublicKey{
350// Created: time.Now().String(),
351// Key: key,
352// Name: name,
353// }},
354// })
355
356// // invalid record
357// if err != nil {
358// h.WriteOOBNotice(w, "keys", "Invalid inputs. Check your formatting and try again.")
359// log.Printf("failed to create record: %s", err)
360// return
361// }
362
363// log.Println("created atproto record: ", resp.Uri)
364
365// h.WriteOOBNotice(w, "keys", "Key added!")
366// return
367// }
368// }
369
370// func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
371// session, _ := h.s.Get(r, "bild-session")
372// did := session.Values["did"].(string)
373// handle := session.Values["handle"].(string)
374
375// switch r.Method {
376// case http.MethodGet:
377// if err := h.t.ExecuteTemplate(w, "repo/new", nil); err != nil {
378// log.Println(err)
379// return
380// }
381// case http.MethodPut:
382// name := r.FormValue("name")
383// description := r.FormValue("description")
384
385// repoPath := filepath.Join(h.c.Repo.ScanPath, did, name)
386// err := git.InitBare(repoPath)
387// if err != nil {
388// h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.")
389// return
390// }
391
392// err = h.db.AddRepo(did, name, description)
393// if err != nil {
394// h.WriteOOBNotice(w, "repo", "Error creating repo. Try again later.")
395// return
396// }
397
398// w.Header().Set("HX-Redirect", fmt.Sprintf("/@%s/%s", handle, name))
399// w.WriteHeader(http.StatusOK)
400// }
401// }
402
403// func (h *Handle) Timeline(w http.ResponseWriter, r *http.Request) {
404// session, err := h.s.Get(r, "bild-session")
405// user := make(map[string]string)
406// if err != nil || session.IsNew {
407// // user is not logged in
408// } else {
409// user["handle"] = session.Values["handle"].(string)
410// user["did"] = session.Values["did"].(string)
411// }
412
413// if err := h.t.ExecuteTemplate(w, "timeline", user); err != nil {
414// log.Println(err)
415// return
416// }
417// }