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