this repo has no description
1package routes
2
3import (
4 "compress/gzip"
5 "fmt"
6 "html/template"
7 "log"
8 "net/http"
9 "os"
10 "path/filepath"
11 "sort"
12 "strconv"
13 "strings"
14 "time"
15
16 "github.com/dustin/go-humanize"
17 "github.com/go-chi/chi/v5"
18 "github.com/icyphox/bild/legit/config"
19 "github.com/icyphox/bild/legit/db"
20 "github.com/icyphox/bild/legit/git"
21 "github.com/russross/blackfriday/v2"
22 "golang.org/x/crypto/ssh"
23)
24
25type Handle struct {
26 c *config.Config
27 t *template.Template
28 db *db.DB
29}
30
31func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
32 user := chi.URLParam(r, "user")
33 path := filepath.Join(h.c.Repo.ScanPath, user)
34 dirs, err := os.ReadDir(path)
35 if err != nil {
36 h.Write500(w)
37 log.Printf("reading scan path: %s", err)
38 return
39 }
40
41 type info struct {
42 DisplayName, Name, Desc, Idle string
43 d time.Time
44 }
45
46 infos := []info{}
47
48 for _, dir := range dirs {
49 name := dir.Name()
50 if !dir.IsDir() || h.isIgnored(name) || h.isUnlisted(name) {
51 continue
52 }
53
54 gr, err := git.Open(path, "")
55 if err != nil {
56 log.Println(err)
57 continue
58 }
59
60 c, err := gr.LastCommit()
61 if err != nil {
62 h.Write500(w)
63 log.Println(err)
64 return
65 }
66
67 infos = append(infos, info{
68 DisplayName: getDisplayName(name),
69 Name: name,
70 Desc: getDescription(path),
71 Idle: humanize.Time(c.Author.When),
72 d: c.Author.When,
73 })
74 }
75
76 sort.Slice(infos, func(i, j int) bool {
77 return infos[j].d.Before(infos[i].d)
78 })
79
80 data := make(map[string]interface{})
81 data["meta"] = h.c.Meta
82 data["info"] = infos
83
84 if err := h.t.ExecuteTemplate(w, "index", data); err != nil {
85 log.Println(err)
86 return
87 }
88}
89
90func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
91 name := uniqueName(r)
92 if h.isIgnored(name) {
93 h.Write404(w)
94 return
95 }
96
97 name = filepath.Clean(name)
98 path := filepath.Join(h.c.Repo.ScanPath, name)
99
100 fmt.Println(path)
101 gr, err := git.Open(path, "")
102 if err != nil {
103 h.Write404(w)
104 return
105 }
106 commits, err := gr.Commits()
107 if err != nil {
108 h.Write500(w)
109 log.Println(err)
110 return
111 }
112
113 var readmeContent template.HTML
114 for _, readme := range h.c.Repo.Readme {
115 ext := filepath.Ext(readme)
116 content, _ := gr.FileContent(readme)
117 if len(content) > 0 {
118 switch ext {
119 case ".md", ".mkd", ".markdown":
120 unsafe := blackfriday.Run(
121 []byte(content),
122 blackfriday.WithExtensions(blackfriday.CommonExtensions),
123 )
124 html := sanitize(unsafe)
125 readmeContent = template.HTML(html)
126 default:
127 safe := sanitize([]byte(content))
128 readmeContent = template.HTML(
129 fmt.Sprintf(`<pre>%s</pre>`, safe),
130 )
131 }
132 break
133 }
134 }
135
136 if readmeContent == "" {
137 log.Printf("no readme found for %s", name)
138 }
139
140 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
141 if err != nil {
142 h.Write500(w)
143 log.Println(err)
144 return
145 }
146
147 if len(commits) >= 3 {
148 commits = commits[:3]
149 }
150
151 data := make(map[string]any)
152 data["name"] = name
153 data["displayname"] = getDisplayName(name)
154 data["ref"] = mainBranch
155 data["readme"] = readmeContent
156 data["commits"] = commits
157 data["desc"] = getDescription(path)
158 data["servername"] = h.c.Server.Name
159 data["meta"] = h.c.Meta
160 data["gomod"] = isGoModule(gr)
161
162 if err := h.t.ExecuteTemplate(w, "repo", data); err != nil {
163 log.Println(err)
164 return
165 }
166
167 return
168}
169
170func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
171 name := uniqueName(r)
172 if h.isIgnored(name) {
173 h.Write404(w)
174 return
175 }
176 treePath := chi.URLParam(r, "*")
177 ref := chi.URLParam(r, "ref")
178
179 name = filepath.Clean(name)
180 path := filepath.Join(h.c.Repo.ScanPath, name)
181 fmt.Println(path)
182 gr, err := git.Open(path, ref)
183 if err != nil {
184 h.Write404(w)
185 return
186 }
187
188 files, err := gr.FileTree(treePath)
189 if err != nil {
190 h.Write500(w)
191 log.Println(err)
192 return
193 }
194
195 data := make(map[string]any)
196 data["name"] = name
197 data["displayname"] = getDisplayName(name)
198 data["ref"] = ref
199 data["parent"] = treePath
200 data["desc"] = getDescription(path)
201 data["dotdot"] = filepath.Dir(treePath)
202
203 h.listFiles(files, data, w)
204 return
205}
206
207func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
208 var raw bool
209 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
210 raw = rawParam
211 }
212
213 name := uniqueName(r)
214
215 if h.isIgnored(name) {
216 h.Write404(w)
217 return
218 }
219 treePath := chi.URLParam(r, "*")
220 ref := chi.URLParam(r, "ref")
221
222 name = filepath.Clean(name)
223 path := filepath.Join(h.c.Repo.ScanPath, name)
224 gr, err := git.Open(path, ref)
225 if err != nil {
226 h.Write404(w)
227 return
228 }
229
230 contents, err := gr.FileContent(treePath)
231 if err != nil {
232 h.Write500(w)
233 return
234 }
235 data := make(map[string]any)
236 data["name"] = name
237 data["displayname"] = getDisplayName(name)
238 data["ref"] = ref
239 data["desc"] = getDescription(path)
240 data["path"] = treePath
241
242 safe := sanitize([]byte(contents))
243
244 if raw {
245 h.showRaw(string(safe), w)
246 } else {
247 if h.c.Meta.SyntaxHighlight == "" {
248 h.showFile(string(safe), data, w)
249 } else {
250 h.showFileWithHighlight(treePath, string(safe), data, w)
251 }
252 }
253}
254
255func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
256 name := uniqueName(r)
257 if h.isIgnored(name) {
258 h.Write404(w)
259 return
260 }
261
262 file := chi.URLParam(r, "file")
263
264 // TODO: extend this to add more files compression (e.g.: xz)
265 if !strings.HasSuffix(file, ".tar.gz") {
266 h.Write404(w)
267 return
268 }
269
270 ref := strings.TrimSuffix(file, ".tar.gz")
271
272 // This allows the browser to use a proper name for the file when
273 // downloading
274 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
275 setContentDisposition(w, filename)
276 setGZipMIME(w)
277
278 path := filepath.Join(h.c.Repo.ScanPath, name)
279 gr, err := git.Open(path, ref)
280 if err != nil {
281 h.Write404(w)
282 return
283 }
284
285 gw := gzip.NewWriter(w)
286 defer gw.Close()
287
288 prefix := fmt.Sprintf("%s-%s", name, ref)
289 err = gr.WriteTar(gw, prefix)
290 if err != nil {
291 // once we start writing to the body we can't report error anymore
292 // so we are only left with printing the error.
293 log.Println(err)
294 return
295 }
296
297 err = gw.Flush()
298 if err != nil {
299 // once we start writing to the body we can't report error anymore
300 // so we are only left with printing the error.
301 log.Println(err)
302 return
303 }
304}
305
306func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
307 name := uniqueName(r)
308 if h.isIgnored(name) {
309 h.Write404(w)
310 return
311 }
312 ref := chi.URLParam(r, "ref")
313
314 path := filepath.Join(h.c.Repo.ScanPath, name)
315 gr, err := git.Open(path, ref)
316 if err != nil {
317 h.Write404(w)
318 return
319 }
320
321 commits, err := gr.Commits()
322 if err != nil {
323 h.Write500(w)
324 log.Println(err)
325 return
326 }
327
328 data := make(map[string]interface{})
329 data["commits"] = commits
330 data["meta"] = h.c.Meta
331 data["name"] = name
332 data["displayname"] = getDisplayName(name)
333 data["ref"] = ref
334 data["desc"] = getDescription(path)
335 data["log"] = true
336
337 if err := h.t.ExecuteTemplate(w, "log", data); err != nil {
338 log.Println(err)
339 return
340 }
341}
342
343func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
344 name := uniqueName(r)
345 if h.isIgnored(name) {
346 h.Write404(w)
347 return
348 }
349 ref := chi.URLParam(r, "ref")
350
351 path := filepath.Join(h.c.Repo.ScanPath, name)
352 gr, err := git.Open(path, ref)
353 if err != nil {
354 h.Write404(w)
355 return
356 }
357
358 diff, err := gr.Diff()
359 if err != nil {
360 h.Write500(w)
361 log.Println(err)
362 return
363 }
364
365 data := make(map[string]interface{})
366
367 data["commit"] = diff.Commit
368 data["stat"] = diff.Stat
369 data["diff"] = diff.Diff
370 data["meta"] = h.c.Meta
371 data["name"] = name
372 data["displayname"] = getDisplayName(name)
373 data["ref"] = ref
374 data["desc"] = getDescription(path)
375
376 if err := h.t.ExecuteTemplate(w, "commit", data); err != nil {
377 log.Println(err)
378 return
379 }
380}
381
382func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
383 name := chi.URLParam(r, "name")
384 if h.isIgnored(name) {
385 h.Write404(w)
386 return
387 }
388
389 path := filepath.Join(h.c.Repo.ScanPath, name)
390 gr, err := git.Open(path, "")
391 if err != nil {
392 h.Write404(w)
393 return
394 }
395
396 tags, err := gr.Tags()
397 if err != nil {
398 // Non-fatal, we *should* have at least one branch to show.
399 log.Println(err)
400 }
401
402 branches, err := gr.Branches()
403 if err != nil {
404 log.Println(err)
405 h.Write500(w)
406 return
407 }
408
409 data := make(map[string]interface{})
410
411 data["meta"] = h.c.Meta
412 data["name"] = name
413 data["displayname"] = getDisplayName(name)
414 data["branches"] = branches
415 data["tags"] = tags
416 data["desc"] = getDescription(path)
417
418 if err := h.t.ExecuteTemplate(w, "refs", data); err != nil {
419 log.Println(err)
420 return
421 }
422}
423
424func (h *Handle) ServeStatic(w http.ResponseWriter, r *http.Request) {
425 f := chi.URLParam(r, "file")
426 f = filepath.Clean(filepath.Join(h.c.Dirs.Static, f))
427
428 http.ServeFile(w, r, f)
429}
430
431func (h *Handle) Login(w http.ResponseWriter, r *http.Request) {
432 if err := h.t.ExecuteTemplate(w, "login", nil); err != nil {
433 log.Println(err)
434 return
435 }
436}
437
438func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
439
440 switch r.Method {
441 case http.MethodGet:
442 // TODO: fetch keys from db
443 if err := h.t.ExecuteTemplate(w, "keys", nil); err != nil {
444 log.Println(err)
445 return
446 }
447 case http.MethodPut:
448 key := r.FormValue("key")
449 name := r.FormValue("name")
450
451 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
452 if err != nil {
453 h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.")
454 log.Printf("parsing public key: %s", err)
455 return
456 }
457
458 // TODO: add did here
459 if err := h.db.AddPublicKey("did:ashtntnashtx", name, key); err != nil {
460 h.WriteOOBNotice(w, "keys", "Failed to add key")
461 log.Printf("adding public key: %s", err)
462 return
463 }
464
465 h.WriteOOBNotice(w, "keys", "Key added!")
466 return
467 }
468}