internal/httpd/readme.go
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}