internal/httpd/readme.go

0db33b1ad160bda8a1fd2900e84c654ba25f1218
gitbay/internal/httpd/readme.go history · blame · raw

114 lines · 3693 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	{"BSD Zero Clause License", "0BSD"},
 27	{"Zero Clause BSD", "0BSD"},
 28	// ISC is the 0BSD grant plus the notice-retention clause; match on the
 29	// clause so title-less 0BSD files don't read as ISC.
 30	{"hereby granted, provided that the above copyright notice", "ISC"},
 31	{"Permission to use, copy, modify, and/or distribute", "0BSD"},
 32	{"This is free and unencumbered software", "Unlicense"},
 33	{"CC0", "CC0"},
 34}
 35
 36// detectLicense reports the repo's license name from a conventional file
 37// at the ref root, or "".
 38func detectLicense(dir, ref string) string {
 39	for _, name := range []string{"LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "UNLICENSE"} {
 40		raw, err := gitutil.ReadBlob(dir, ref, name, 2048)
 41		if err != nil {
 42			continue
 43		}
 44		// Collapse whitespace so markers match across line wraps.
 45		text := strings.Join(strings.Fields(string(raw)), " ")
 46		for _, c := range licenseChecks {
 47			if strings.Contains(text, c.marker) {
 48				return c.name
 49			}
 50		}
 51		return "license"
 52	}
 53	return ""
 54}
 55
 56// rewriteRelativeLinks makes relative hrefs and srcs in rendered repo
 57// content resolve on the forge: links go to blob pages, images to raw.
 58// go-org exports .org links as .html, so an .html target whose .org (or
 59// .md) source exists in the tree maps back to the source file.
 60func rewriteRelativeLinks(rendered template.HTML, p repoPage, baseDir string) template.HTML {
 61	ctx := &html.Node{Type: html.ElementNode, Data: "div", DataAtom: atom.Div}
 62	nodes, err := html.ParseFragment(strings.NewReader(string(rendered)), ctx)
 63	if err != nil {
 64		return rendered
 65	}
 66	var walk func(*html.Node)
 67	walk = func(n *html.Node) {
 68		if n.Type == html.ElementNode {
 69			for i, a := range n.Attr {
 70				isHref := a.Key == "href" && n.Data == "a"
 71				isSrc := a.Key == "src" && (n.Data == "img" || n.Data == "video" || n.Data == "source")
 72				if !isHref && !isSrc {
 73					continue
 74				}
 75				v := a.Val
 76				if v == "" || strings.Contains(v, "://") || strings.HasPrefix(v, "/") ||
 77					strings.HasPrefix(v, "#") || strings.HasPrefix(v, "mailto:") ||
 78					strings.HasPrefix(v, "data:") {
 79					continue
 80				}
 81				target := path.Clean(path.Join(baseDir, v))
 82				if strings.HasPrefix(target, "..") {
 83					continue
 84				}
 85				if isSrc {
 86					n.Attr[i].Val = "/" + p.Repo.Path() + "/raw/" + p.Ref + "/" + target
 87					continue
 88				}
 89				// .html from org/markdown exports maps back to the source.
 90				if strings.HasSuffix(target, ".html") {
 91					stem := strings.TrimSuffix(target, ".html")
 92					for _, ext := range []string{".org", ".md"} {
 93						if _, err := gitutil.ReadBlob(p.Dir, p.Ref, stem+ext, 1); err == nil {
 94							target = stem + ext
 95							break
 96						}
 97					}
 98				}
 99				n.Attr[i].Val = "/" + p.Repo.Path() + "/blob/" + p.Ref + "/" + target
100			}
101		}
102		for c := n.FirstChild; c != nil; c = c.NextSibling {
103			walk(c)
104		}
105	}
106	var out strings.Builder
107	for _, n := range nodes {
108		walk(n)
109		if err := html.Render(&out, n); err != nil {
110			return rendered
111		}
112	}
113	return template.HTML(out.String())
114}