all repos — legit @ 9b685c8db2bb63a7c09d29814a933128b4ec212a

Unnamed repository; edit this file 'description' to name the repository.

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