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