+10
-5
appview/pages/markup/camo.go
+10
-5
appview/pages/markup/camo.go
···
17
return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL)
18
}
19
20
-
func (rctx *RenderContext) camoImageLinkTransformer(img *ast.Image) {
21
// don't camo on dev
22
if rctx.IsDev {
23
-
return
24
}
25
26
-
dst := string(img.Destination)
27
-
28
if rctx.CamoUrl != "" && rctx.CamoSecret != "" {
29
-
img.Destination = []byte(generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst))
30
}
31
}
···
17
return fmt.Sprintf("%s/%s/%s", baseURL, signature, hexURL)
18
}
19
20
+
func (rctx *RenderContext) camoImageLinkTransformer(dst string) string {
21
// don't camo on dev
22
if rctx.IsDev {
23
+
return dst
24
}
25
26
if rctx.CamoUrl != "" && rctx.CamoSecret != "" {
27
+
return generateCamoURL(rctx.CamoUrl, rctx.CamoSecret, dst)
28
}
29
+
30
+
return dst
31
+
}
32
+
33
+
func (rctx *RenderContext) camoImageLinkAstTransformer(img *ast.Image) {
34
+
dst := string(img.Destination)
35
+
img.Destination = []byte(rctx.camoImageLinkTransformer(dst))
36
}
+89
-10
appview/pages/markup/markdown.go
+89
-10
appview/pages/markup/markdown.go
···
3
4
import (
5
"bytes"
6
"net/url"
7
"path"
8
9
"github.com/microcosm-cc/bluemonday"
10
"github.com/yuin/goldmark"
···
14
"github.com/yuin/goldmark/renderer/html"
15
"github.com/yuin/goldmark/text"
16
"github.com/yuin/goldmark/util"
17
18
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
19
)
···
61
if err := md.Convert([]byte(source), &buf); err != nil {
62
return source
63
}
64
-
return buf.String()
65
}
66
67
func (rctx *RenderContext) Sanitize(html string) string {
···
101
case *ast.Link:
102
a.rctx.relativeLinkTransformer(n)
103
case *ast.Image:
104
-
a.rctx.imageFromKnotTransformer(n)
105
-
a.rctx.camoImageLinkTransformer(n)
106
}
107
case RendererTypeDefault:
108
switch n := n.(type) {
109
case *ast.Image:
110
-
a.rctx.imageFromKnotTransformer(n)
111
-
a.rctx.camoImageLinkTransformer(n)
112
}
113
}
114
···
130
link.Destination = []byte(newPath)
131
}
132
133
-
func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) {
134
-
dst := string(img.Destination)
135
-
136
if isAbsoluteUrl(dst) {
137
-
return
138
}
139
140
scheme := "https"
···
155
actualPath),
156
}
157
newPath := parsedURL.String()
158
-
img.Destination = []byte(newPath)
159
}
160
161
// actualPath decides when to join the file path with the
···
3
4
import (
5
"bytes"
6
+
"fmt"
7
+
"io"
8
"net/url"
9
"path"
10
+
"strings"
11
12
"github.com/microcosm-cc/bluemonday"
13
"github.com/yuin/goldmark"
···
17
"github.com/yuin/goldmark/renderer/html"
18
"github.com/yuin/goldmark/text"
19
"github.com/yuin/goldmark/util"
20
+
htmlparse "golang.org/x/net/html"
21
22
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
23
)
···
65
if err := md.Convert([]byte(source), &buf); err != nil {
66
return source
67
}
68
+
69
+
var processed strings.Builder
70
+
if err := postProcess(rctx, strings.NewReader(buf.String()), &processed); err != nil {
71
+
return source
72
+
}
73
+
74
+
return processed.String()
75
+
}
76
+
77
+
func postProcess(ctx *RenderContext, input io.Reader, output io.Writer) error {
78
+
node, err := htmlparse.Parse(io.MultiReader(
79
+
strings.NewReader("<html><body>"),
80
+
input,
81
+
strings.NewReader("</body></html>"),
82
+
))
83
+
if err != nil {
84
+
return fmt.Errorf("failed to parse html: %w", err)
85
+
}
86
+
87
+
if node.Type == htmlparse.DocumentNode {
88
+
node = node.FirstChild
89
+
}
90
+
91
+
visitNode(ctx, node)
92
+
93
+
newNodes := make([]*htmlparse.Node, 0, 5)
94
+
95
+
if node.Data == "html" {
96
+
node = node.FirstChild
97
+
for node != nil && node.Data != "body" {
98
+
node = node.NextSibling
99
+
}
100
+
}
101
+
if node != nil {
102
+
if node.Data == "body" {
103
+
child := node.FirstChild
104
+
for child != nil {
105
+
newNodes = append(newNodes, child)
106
+
child = child.NextSibling
107
+
}
108
+
} else {
109
+
newNodes = append(newNodes, node)
110
+
}
111
+
}
112
+
113
+
for _, node := range newNodes {
114
+
if err := htmlparse.Render(output, node); err != nil {
115
+
return fmt.Errorf("failed to render processed html: %w", err)
116
+
}
117
+
}
118
+
119
+
return nil
120
+
}
121
+
122
+
func visitNode(ctx *RenderContext, node *htmlparse.Node) {
123
+
switch node.Type {
124
+
case htmlparse.ElementNode:
125
+
if node.Data == "img" {
126
+
for i, attr := range node.Attr {
127
+
if attr.Key != "src" {
128
+
continue
129
+
}
130
+
attr.Val = ctx.imageFromKnotTransformer(attr.Val)
131
+
attr.Val = ctx.camoImageLinkTransformer(attr.Val)
132
+
node.Attr[i] = attr
133
+
}
134
+
}
135
+
136
+
for n := node.FirstChild; n != nil; n = n.NextSibling {
137
+
visitNode(ctx, n)
138
+
}
139
+
default:
140
+
}
141
}
142
143
func (rctx *RenderContext) Sanitize(html string) string {
···
177
case *ast.Link:
178
a.rctx.relativeLinkTransformer(n)
179
case *ast.Image:
180
+
a.rctx.imageFromKnotAstTransformer(n)
181
+
a.rctx.camoImageLinkAstTransformer(n)
182
}
183
case RendererTypeDefault:
184
switch n := n.(type) {
185
case *ast.Image:
186
+
a.rctx.imageFromKnotAstTransformer(n)
187
+
a.rctx.camoImageLinkAstTransformer(n)
188
}
189
}
190
···
206
link.Destination = []byte(newPath)
207
}
208
209
+
func (rctx *RenderContext) imageFromKnotTransformer(dst string) string {
210
if isAbsoluteUrl(dst) {
211
+
return dst
212
}
213
214
scheme := "https"
···
229
actualPath),
230
}
231
newPath := parsedURL.String()
232
+
return newPath
233
+
}
234
+
235
+
func (rctx *RenderContext) imageFromKnotAstTransformer(img *ast.Image) {
236
+
dst := string(img.Destination)
237
+
img.Destination = []byte(rctx.imageFromKnotTransformer(dst))
238
}
239
240
// actualPath decides when to join the file path with the