routes/routes.go (view raw)
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 "git.icyphox.sh/legit/config"
17 "git.icyphox.sh/legit/git"
18 "github.com/dustin/go-humanize"
19 "github.com/microcosm-cc/bluemonday"
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.isHidden(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 := r.PathValue("name")
90 if d.isIgnored(name) {
91 d.Write404(w)
92 return
93 }
94 name = filepath.Clean(name)
95 path := filepath.Join(d.c.Repo.ScanPath, name)
96
97 gr, err := git.Open(path, "")
98 if err != nil {
99 d.Write404(w)
100 return
101 }
102
103 commits, err := gr.Commits()
104 if err != nil {
105 d.Write500(w)
106 log.Println(err)
107 return
108 }
109
110 var readmeContent template.HTML
111 for _, readme := range d.c.Repo.Readme {
112 ext := filepath.Ext(readme)
113 content, _ := gr.FileContent(readme)
114 if len(content) > 0 {
115 switch ext {
116 case ".md", ".mkd", ".markdown":
117 unsafe := blackfriday.Run(
118 []byte(content),
119 blackfriday.WithExtensions(blackfriday.CommonExtensions),
120 )
121 html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
122 readmeContent = template.HTML(html)
123 default:
124 readmeContent = template.HTML(
125 fmt.Sprintf(`<pre>%s</pre>`, content),
126 )
127 }
128 break
129 }
130 }
131
132 if readmeContent == "" {
133 log.Printf("no readme found for %s", name)
134 }
135
136 mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
137 if err != nil {
138 d.Write500(w)
139 log.Println(err)
140 return
141 }
142
143 tpath := filepath.Join(d.c.Dirs.Templates, "*")
144 t := template.Must(template.ParseGlob(tpath))
145
146 if len(commits) >= 3 {
147 commits = commits[:3]
148 }
149
150 data := make(map[string]any)
151 data["name"] = name
152 data["displayname"] = getDisplayName(name)
153 data["ref"] = mainBranch
154 data["readme"] = readmeContent
155 data["commits"] = commits
156 data["desc"] = getDescription(path)
157 data["servername"] = d.c.Server.Name
158 data["meta"] = d.c.Meta
159 data["gomod"] = isGoModule(gr)
160
161 if err := t.ExecuteTemplate(w, "repo", data); err != nil {
162 log.Println(err)
163 return
164 }
165
166 return
167}
168
169func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
170 name := r.PathValue("name")
171 if d.isIgnored(name) {
172 d.Write404(w)
173 return
174 }
175 treePath := r.PathValue("rest")
176 ref := r.PathValue("ref")
177
178 name = filepath.Clean(name)
179 path := filepath.Join(d.c.Repo.ScanPath, name)
180 gr, err := git.Open(path, ref)
181 if err != nil {
182 d.Write404(w)
183 return
184 }
185
186 files, err := gr.FileTree(treePath)
187 if err != nil {
188 d.Write500(w)
189 log.Println(err)
190 return
191 }
192
193 data := make(map[string]any)
194 data["name"] = name
195 data["displayname"] = getDisplayName(name)
196 data["ref"] = ref
197 data["parent"] = treePath
198 data["desc"] = getDescription(path)
199 data["dotdot"] = filepath.Dir(treePath)
200
201 d.listFiles(files, data, w)
202 return
203}
204
205func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
206 var raw bool
207 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
208 raw = rawParam
209 }
210
211 name := r.PathValue("name")
212 if d.isIgnored(name) {
213 d.Write404(w)
214 return
215 }
216 treePath := r.PathValue("rest")
217 ref := r.PathValue("ref")
218
219 name = filepath.Clean(name)
220 path := filepath.Join(d.c.Repo.ScanPath, name)
221 gr, err := git.Open(path, ref)
222 if err != nil {
223 d.Write404(w)
224 return
225 }
226
227 contents, err := gr.FileContent(treePath)
228 data := make(map[string]any)
229 data["name"] = name
230 data["displayname"] = getDisplayName(name)
231 data["ref"] = ref
232 data["desc"] = getDescription(path)
233 data["path"] = treePath
234
235 if raw {
236 d.showRaw(contents, w)
237 } else {
238 d.showFile(contents, data, w)
239 }
240 return
241}
242
243func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
244 name := r.PathValue("name")
245 if d.isIgnored(name) {
246 d.Write404(w)
247 return
248 }
249
250 file := r.PathValue("file")
251
252 // TODO: extend this to add more files compression (e.g.: xz)
253 if !strings.HasSuffix(file, ".tar.gz") {
254 d.Write404(w)
255 return
256 }
257
258 ref := strings.TrimSuffix(file, ".tar.gz")
259
260 // This allows the browser to use a proper name for the file when
261 // downloading
262 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
263 setContentDisposition(w, filename)
264 setGZipMIME(w)
265
266 path := filepath.Join(d.c.Repo.ScanPath, name)
267 gr, err := git.Open(path, ref)
268 if err != nil {
269 d.Write404(w)
270 return
271 }
272
273 gw := gzip.NewWriter(w)
274 defer gw.Close()
275
276 prefix := fmt.Sprintf("%s-%s", name, ref)
277 err = gr.WriteTar(gw, prefix)
278 if err != nil {
279 // once we start writing to the body we can't report error anymore
280 // so we are only left with printing the error.
281 log.Println(err)
282 return
283 }
284
285 err = gw.Flush()
286 if err != nil {
287 // once we start writing to the body we can't report error anymore
288 // so we are only left with printing the error.
289 log.Println(err)
290 return
291 }
292}
293
294func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
295 name := r.PathValue("name")
296 if d.isIgnored(name) {
297 d.Write404(w)
298 return
299 }
300 ref := r.PathValue("ref")
301
302 path := filepath.Join(d.c.Repo.ScanPath, name)
303 gr, err := git.Open(path, ref)
304 if err != nil {
305 d.Write404(w)
306 return
307 }
308
309 commits, err := gr.Commits()
310 if err != nil {
311 d.Write500(w)
312 log.Println(err)
313 return
314 }
315
316 tpath := filepath.Join(d.c.Dirs.Templates, "*")
317 t := template.Must(template.ParseGlob(tpath))
318
319 data := make(map[string]interface{})
320 data["commits"] = commits
321 data["meta"] = d.c.Meta
322 data["name"] = name
323 data["displayname"] = getDisplayName(name)
324 data["ref"] = ref
325 data["desc"] = getDescription(path)
326 data["log"] = true
327
328 if err := t.ExecuteTemplate(w, "log", data); err != nil {
329 log.Println(err)
330 return
331 }
332}
333
334func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
335 name := r.PathValue("name")
336 if d.isIgnored(name) {
337 d.Write404(w)
338 return
339 }
340 ref := r.PathValue("ref")
341
342 path := filepath.Join(d.c.Repo.ScanPath, name)
343 gr, err := git.Open(path, ref)
344 if err != nil {
345 d.Write404(w)
346 return
347 }
348
349 diff, err := gr.Diff()
350 if err != nil {
351 d.Write500(w)
352 log.Println(err)
353 return
354 }
355
356 tpath := filepath.Join(d.c.Dirs.Templates, "*")
357 t := template.Must(template.ParseGlob(tpath))
358
359 data := make(map[string]interface{})
360
361 data["commit"] = diff.Commit
362 data["stat"] = diff.Stat
363 data["diff"] = diff.Diff
364 data["meta"] = d.c.Meta
365 data["name"] = name
366 data["displayname"] = getDisplayName(name)
367 data["ref"] = ref
368 data["desc"] = getDescription(path)
369
370 if err := t.ExecuteTemplate(w, "commit", data); err != nil {
371 log.Println(err)
372 return
373 }
374}
375
376func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
377 name := r.PathValue("name")
378 if d.isIgnored(name) {
379 d.Write404(w)
380 return
381 }
382
383 path := filepath.Join(d.c.Repo.ScanPath, name)
384 gr, err := git.Open(path, "")
385 if err != nil {
386 d.Write404(w)
387 return
388 }
389
390 tags, err := gr.Tags()
391 if err != nil {
392 // Non-fatal, we *should* have at least one branch to show.
393 log.Println(err)
394 }
395
396 branches, err := gr.Branches()
397 if err != nil {
398 log.Println(err)
399 d.Write500(w)
400 return
401 }
402
403 tpath := filepath.Join(d.c.Dirs.Templates, "*")
404 t := template.Must(template.ParseGlob(tpath))
405
406 data := make(map[string]interface{})
407
408 data["meta"] = d.c.Meta
409 data["name"] = name
410 data["displayname"] = getDisplayName(name)
411 data["branches"] = branches
412 data["tags"] = tags
413 data["desc"] = getDescription(path)
414
415 if err := t.ExecuteTemplate(w, "refs", data); err != nil {
416 log.Println(err)
417 return
418 }
419}
420
421func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
422 f := r.PathValue("file")
423 f = filepath.Clean(filepath.Join(d.c.Dirs.Static, f))
424
425 http.ServeFile(w, r, f)
426}