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