all repos — legit @ e219000dfe52536fcafbe81069c6572a992e6294

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.Author.When),
 74			d:           c.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	return
180}
181
182func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
183	name := r.PathValue("name")
184	if d.isIgnored(name) {
185		d.Write404(w)
186		return
187	}
188	treePath := r.PathValue("rest")
189	ref := r.PathValue("ref")
190
191	name = filepath.Clean(name)
192	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
193	if err != nil {
194		log.Printf("securejoin error: %v", err)
195		d.Write404(w)
196		return
197	}
198	gr, err := git.Open(path, ref)
199	if err != nil {
200		d.Write404(w)
201		return
202	}
203
204	files, err := gr.FileTree(treePath)
205	if err != nil {
206		d.Write500(w)
207		log.Println(err)
208		return
209	}
210
211	data := make(map[string]any)
212	data["name"] = name
213	data["displayname"] = getDisplayName(name)
214	data["ref"] = ref
215	data["parent"] = treePath
216	data["desc"] = getDescription(path)
217	data["dotdot"] = filepath.Dir(treePath)
218
219	d.listFiles(files, data, w)
220	return
221}
222
223func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
224	var raw bool
225	if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
226		raw = rawParam
227	}
228
229	name := r.PathValue("name")
230	if d.isIgnored(name) {
231		d.Write404(w)
232		return
233	}
234	treePath := r.PathValue("rest")
235	ref := r.PathValue("ref")
236
237	name = filepath.Clean(name)
238	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
239	if err != nil {
240		log.Printf("securejoin error: %v", err)
241		d.Write404(w)
242		return
243	}
244
245	gr, err := git.Open(path, ref)
246	if err != nil {
247		d.Write404(w)
248		return
249	}
250
251	contents, err := gr.FileContent(treePath)
252	if err != nil {
253		d.Write500(w)
254		return
255	}
256	data := make(map[string]any)
257	data["name"] = name
258	data["displayname"] = getDisplayName(name)
259	data["ref"] = ref
260	data["desc"] = getDescription(path)
261	data["path"] = treePath
262
263	if raw {
264		d.showRaw(contents, w)
265	} else {
266		if d.c.Meta.SyntaxHighlight == "" {
267			d.showFile(contents, data, w)
268		} else {
269			d.showFileWithHighlight(treePath, contents, data, w)
270		}
271	}
272}
273
274func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
275	name := r.PathValue("name")
276	if d.isIgnored(name) {
277		d.Write404(w)
278		return
279	}
280
281	file := r.PathValue("file")
282
283	// TODO: extend this to add more files compression (e.g.: xz)
284	if !strings.HasSuffix(file, ".tar.gz") {
285		d.Write404(w)
286		return
287	}
288
289	ref := strings.TrimSuffix(file, ".tar.gz")
290
291	// This allows the browser to use a proper name for the file when
292	// downloading
293	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
294	setContentDisposition(w, filename)
295	setGZipMIME(w)
296
297	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
298	if err != nil {
299		log.Printf("securejoin error: %v", err)
300		d.Write404(w)
301		return
302	}
303
304	gr, err := git.Open(path, ref)
305	if err != nil {
306		d.Write404(w)
307		return
308	}
309
310	gw := gzip.NewWriter(w)
311	defer gw.Close()
312
313	prefix := fmt.Sprintf("%s-%s", name, ref)
314	err = gr.WriteTar(gw, prefix)
315	if err != nil {
316		// once we start writing to the body we can't report error anymore
317		// so we are only left with printing the error.
318		log.Println(err)
319		return
320	}
321
322	err = gw.Flush()
323	if err != nil {
324		// once we start writing to the body we can't report error anymore
325		// so we are only left with printing the error.
326		log.Println(err)
327		return
328	}
329}
330
331func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
332	name := r.PathValue("name")
333	if d.isIgnored(name) {
334		d.Write404(w)
335		return
336	}
337	ref := r.PathValue("ref")
338
339	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
340	if err != nil {
341		log.Printf("securejoin error: %v", err)
342		d.Write404(w)
343		return
344	}
345
346	gr, err := git.Open(path, ref)
347	if err != nil {
348		d.Write404(w)
349		return
350	}
351
352	commits, err := gr.Commits()
353	if err != nil {
354		d.Write500(w)
355		log.Println(err)
356		return
357	}
358
359	tpath := filepath.Join(d.c.Dirs.Templates, "*")
360	t := template.Must(template.ParseGlob(tpath))
361
362	data := make(map[string]interface{})
363	data["commits"] = commits
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	data["log"] = true
370
371	if err := t.ExecuteTemplate(w, "log", data); err != nil {
372		log.Println(err)
373		return
374	}
375}
376
377func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
378	name := r.PathValue("name")
379	if d.isIgnored(name) {
380		d.Write404(w)
381		return
382	}
383	ref := r.PathValue("ref")
384
385	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
386	if err != nil {
387		log.Printf("securejoin error: %v", err)
388		d.Write404(w)
389		return
390	}
391	gr, err := git.Open(path, ref)
392	if err != nil {
393		d.Write404(w)
394		return
395	}
396
397	diff, err := gr.Diff()
398	if err != nil {
399		d.Write500(w)
400		log.Println(err)
401		return
402	}
403
404	tpath := filepath.Join(d.c.Dirs.Templates, "*")
405	t := template.Must(template.ParseGlob(tpath))
406
407	data := make(map[string]interface{})
408
409	data["commit"] = diff.Commit
410	data["stat"] = diff.Stat
411	data["diff"] = diff.Diff
412	data["meta"] = d.c.Meta
413	data["name"] = name
414	data["displayname"] = getDisplayName(name)
415	data["ref"] = ref
416	data["desc"] = getDescription(path)
417
418	if err := t.ExecuteTemplate(w, "commit", data); err != nil {
419		log.Println(err)
420		return
421	}
422}
423
424func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
425	name := r.PathValue("name")
426	if d.isIgnored(name) {
427		d.Write404(w)
428		return
429	}
430
431	path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
432	if err != nil {
433		log.Printf("securejoin error: %v", err)
434		d.Write404(w)
435		return
436	}
437
438	gr, err := git.Open(path, "")
439	if err != nil {
440		d.Write404(w)
441		return
442	}
443
444	tags, err := gr.Tags()
445	if err != nil {
446		// Non-fatal, we *should* have at least one branch to show.
447		log.Println(err)
448	}
449
450	branches, err := gr.Branches()
451	if err != nil {
452		log.Println(err)
453		d.Write500(w)
454		return
455	}
456
457	tpath := filepath.Join(d.c.Dirs.Templates, "*")
458	t := template.Must(template.ParseGlob(tpath))
459
460	data := make(map[string]interface{})
461
462	data["meta"] = d.c.Meta
463	data["name"] = name
464	data["displayname"] = getDisplayName(name)
465	data["branches"] = branches
466	data["tags"] = tags
467	data["desc"] = getDescription(path)
468
469	if err := t.ExecuteTemplate(w, "refs", data); err != nil {
470		log.Println(err)
471		return
472	}
473}
474
475func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
476	f := r.PathValue("file")
477	f = filepath.Clean(f)
478	f, err := securejoin.SecureJoin(d.c.Dirs.Static, f)
479	if err != nil {
480		d.Write404(w)
481		return
482	}
483
484	http.ServeFile(w, r, f)
485}