all repos — legit @ 5ba4a5747fdd66ac0ddf5742cf4c275b808df36d

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