internal/httpd/readme.go

471902dd1405f81f8414ea60ae8526cda92387cc
gitbay/internal/httpd/readme.go history · blame · raw

108 lines · 3330 bytes

  1package httpd
  2
  3import (
  4	"html/template"
  5	"path"
  6	"strings"
  7
  8	"golang.org/x/net/html"
  9	"golang.org/x/net/html/atom"
 10
 11	"gitbay.org/gitbay/internal/gitutil"
 12)
 13
 14// licenseNames maps content markers to display names, checked in order.
 15var licenseChecks = []struct{ marker, name string }{
 16	{"MIT License", "MIT"},
 17	{"Permission is hereby granted, free of charge", "MIT"},
 18	{"Apache License", "Apache-2.0"},
 19	{"GNU AFFERO GENERAL PUBLIC LICENSE", "AGPL-3.0"},
 20	{"GNU GENERAL PUBLIC LICENSE", "GPL"},
 21	{"GNU LESSER GENERAL PUBLIC LICENSE", "LGPL"},
 22	{"Mozilla Public License", "MPL-2.0"},
 23	{"BSD 3-Clause", "BSD-3-Clause"},
 24	{"BSD 2-Clause", "BSD-2-Clause"},
 25	{"Redistribution and use in source and binary forms", "BSD"},
 26	{"Permission to use, copy, modify, and/or distribute", "ISC"},
 27	{"This is free and unencumbered software", "Unlicense"},
 28	{"CC0", "CC0"},
 29}
 30
 31// detectLicense reports the repo's license name from a conventional file
 32// at the ref root, or "".
 33func detectLicense(dir, ref string) string {
 34	for _, name := range []string{"LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "UNLICENSE"} {
 35		raw, err := gitutil.ReadBlob(dir, ref, name, 2048)
 36		if err != nil {
 37			continue
 38		}
 39		text := string(raw)
 40		for _, c := range licenseChecks {
 41			if strings.Contains(text, c.marker) {
 42				return c.name
 43			}
 44		}
 45		return "license"
 46	}
 47	return ""
 48}
 49
 50// rewriteRelativeLinks makes relative hrefs and srcs in rendered repo
 51// content resolve on the forge: links go to blob pages, images to raw.
 52// go-org exports .org links as .html, so an .html target whose .org (or
 53// .md) source exists in the tree maps back to the source file.
 54func rewriteRelativeLinks(rendered template.HTML, p repoPage, baseDir string) template.HTML {
 55	ctx := &html.Node{Type: html.ElementNode, Data: "div", DataAtom: atom.Div}
 56	nodes, err := html.ParseFragment(strings.NewReader(string(rendered)), ctx)
 57	if err != nil {
 58		return rendered
 59	}
 60	var walk func(*html.Node)
 61	walk = func(n *html.Node) {
 62		if n.Type == html.ElementNode {
 63			for i, a := range n.Attr {
 64				isHref := a.Key == "href" && n.Data == "a"
 65				isSrc := a.Key == "src" && (n.Data == "img" || n.Data == "video" || n.Data == "source")
 66				if !isHref && !isSrc {
 67					continue
 68				}
 69				v := a.Val
 70				if v == "" || strings.Contains(v, "://") || strings.HasPrefix(v, "/") ||
 71					strings.HasPrefix(v, "#") || strings.HasPrefix(v, "mailto:") ||
 72					strings.HasPrefix(v, "data:") {
 73					continue
 74				}
 75				target := path.Clean(path.Join(baseDir, v))
 76				if strings.HasPrefix(target, "..") {
 77					continue
 78				}
 79				if isSrc {
 80					n.Attr[i].Val = "/" + p.Repo.Path() + "/raw/" + p.Ref + "/" + target
 81					continue
 82				}
 83				// .html from org/markdown exports maps back to the source.
 84				if strings.HasSuffix(target, ".html") {
 85					stem := strings.TrimSuffix(target, ".html")
 86					for _, ext := range []string{".org", ".md"} {
 87						if _, err := gitutil.ReadBlob(p.Dir, p.Ref, stem+ext, 1); err == nil {
 88							target = stem + ext
 89							break
 90						}
 91					}
 92				}
 93				n.Attr[i].Val = "/" + p.Repo.Path() + "/blob/" + p.Ref + "/" + target
 94			}
 95		}
 96		for c := n.FirstChild; c != nil; c = c.NextSibling {
 97			walk(c)
 98		}
 99	}
100	var out strings.Builder
101	for _, n := range nodes {
102		walk(n)
103		if err := html.Render(&out, n); err != nil {
104			return rendered
105		}
106	}
107	return template.HTML(out.String())
108}