internal/httpd/readme.go

9df917e73a67d15adecc3f45976690f6fcd4e47a
gitbay/internal/httpd/readme.go history · blame · raw

72 lines · 2139 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// rewriteRelativeLinks makes relative hrefs and srcs in rendered repo
15// content resolve on the forge: links go to blob pages, images to raw.
16// go-org exports .org links as .html, so an .html target whose .org (or
17// .md) source exists in the tree maps back to the source file.
18func rewriteRelativeLinks(rendered template.HTML, p repoPage, baseDir string) template.HTML {
19	ctx := &html.Node{Type: html.ElementNode, Data: "div", DataAtom: atom.Div}
20	nodes, err := html.ParseFragment(strings.NewReader(string(rendered)), ctx)
21	if err != nil {
22		return rendered
23	}
24	var walk func(*html.Node)
25	walk = func(n *html.Node) {
26		if n.Type == html.ElementNode {
27			for i, a := range n.Attr {
28				isHref := a.Key == "href" && n.Data == "a"
29				isSrc := a.Key == "src" && (n.Data == "img" || n.Data == "video" || n.Data == "source")
30				if !isHref && !isSrc {
31					continue
32				}
33				v := a.Val
34				if v == "" || strings.Contains(v, "://") || strings.HasPrefix(v, "/") ||
35					strings.HasPrefix(v, "#") || strings.HasPrefix(v, "mailto:") ||
36					strings.HasPrefix(v, "data:") {
37					continue
38				}
39				target := path.Clean(path.Join(baseDir, v))
40				if strings.HasPrefix(target, "..") {
41					continue
42				}
43				if isSrc {
44					n.Attr[i].Val = "/" + p.Repo.Path() + "/raw/" + p.Ref + "/" + target
45					continue
46				}
47				// .html from org/markdown exports maps back to the source.
48				if strings.HasSuffix(target, ".html") {
49					stem := strings.TrimSuffix(target, ".html")
50					for _, ext := range []string{".org", ".md"} {
51						if _, err := gitutil.ReadBlob(p.Dir, p.Ref, stem+ext, 1); err == nil {
52							target = stem + ext
53							break
54						}
55					}
56				}
57				n.Attr[i].Val = "/" + p.Repo.Path() + "/blob/" + p.Ref + "/" + target
58			}
59		}
60		for c := n.FirstChild; c != nil; c = c.NextSibling {
61			walk(c)
62		}
63	}
64	var out strings.Builder
65	for _, n := range nodes {
66		walk(n)
67		if err := html.Render(&out, n); err != nil {
68			return rendered
69		}
70	}
71	return template.HTML(out.String())
72}