+50
-15
appview/pages/markup/markdown.go
+50
-15
appview/pages/markup/markdown.go
···
6
6
"net/url"
7
7
"path"
8
8
9
+
"github.com/microcosm-cc/bluemonday"
9
10
"github.com/yuin/goldmark"
10
11
"github.com/yuin/goldmark/ast"
11
12
"github.com/yuin/goldmark/extension"
···
13
14
"github.com/yuin/goldmark/renderer/html"
14
15
"github.com/yuin/goldmark/text"
15
16
"github.com/yuin/goldmark/util"
17
+
16
18
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
17
19
)
18
20
···
62
64
return buf.String()
63
65
}
64
66
67
+
func (rctx *RenderContext) Sanitize(html string) string {
68
+
policy := bluemonday.UGCPolicy()
69
+
policy.AllowAttrs("align", "style").Globally()
70
+
policy.AllowStyles(
71
+
"margin",
72
+
"padding",
73
+
"text-align",
74
+
"font-weight",
75
+
"text-decoration",
76
+
"padding-left",
77
+
"padding-right",
78
+
"padding-top",
79
+
"padding-bottom",
80
+
"margin-left",
81
+
"margin-right",
82
+
"margin-top",
83
+
"margin-bottom",
84
+
)
85
+
return policy.Sanitize(html)
86
+
}
87
+
65
88
type MarkdownTransformer struct {
66
89
rctx *RenderContext
67
90
}
···
74
97
75
98
switch a.rctx.RendererType {
76
99
case RendererTypeRepoMarkdown:
77
-
switch n.(type) {
100
+
switch n := n.(type) {
78
101
case *ast.Link:
79
-
a.rctx.relativeLinkTransformer(n.(*ast.Link))
102
+
a.rctx.relativeLinkTransformer(n)
80
103
case *ast.Image:
81
-
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
82
-
a.rctx.camoImageLinkTransformer(n.(*ast.Image))
104
+
a.rctx.imageFromKnotTransformer(n)
105
+
a.rctx.camoImageLinkTransformer(n)
83
106
}
84
-
85
107
case RendererTypeDefault:
86
-
switch n.(type) {
108
+
switch n := n.(type) {
87
109
case *ast.Image:
88
-
a.rctx.imageFromKnotTransformer(n.(*ast.Image))
89
-
a.rctx.camoImageLinkTransformer(n.(*ast.Image))
110
+
a.rctx.imageFromKnotTransformer(n)
111
+
a.rctx.camoImageLinkTransformer(n)
90
112
}
91
113
}
92
114
···
95
117
}
96
118
97
119
func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) {
120
+
98
121
dst := string(link.Destination)
99
122
100
123
if isAbsoluteUrl(dst) {
101
124
return
102
125
}
103
126
104
-
newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, dst)
127
+
actualPath := rctx.actualPath(dst)
128
+
129
+
newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, actualPath)
105
130
link.Destination = []byte(newPath)
106
131
}
107
132
···
112
137
return
113
138
}
114
139
115
-
// strip leading './'
116
-
if len(dst) >= 2 && dst[0:2] == "./" {
117
-
dst = dst[2:]
118
-
}
119
-
120
140
scheme := "https"
121
141
if rctx.IsDev {
122
142
scheme = "http"
123
143
}
144
+
145
+
actualPath := rctx.actualPath(dst)
146
+
124
147
parsedURL := &url.URL{
125
148
Scheme: scheme,
126
149
Host: rctx.Knot,
···
129
152
rctx.RepoInfo.Name,
130
153
"raw",
131
154
url.PathEscape(rctx.RepoInfo.Ref),
132
-
dst),
155
+
actualPath),
133
156
}
134
157
newPath := parsedURL.String()
135
158
img.Destination = []byte(newPath)
159
+
}
160
+
161
+
// actualPath decides when to join the file path with the
162
+
// current repository directory (essentially only when the link
163
+
// destination is relative. if it's absolute then we assume the
164
+
// user knows what they're doing.)
165
+
func (rctx *RenderContext) actualPath(dst string) string {
166
+
if path.IsAbs(dst) {
167
+
return dst
168
+
}
169
+
170
+
return path.Join(rctx.CurrentDir, dst)
136
171
}
137
172
138
173
func isAbsoluteUrl(link string) bool {
+3
-2
appview/pages/pages.go
+3
-2
appview/pages/pages.go
···
432
432
case ".md", ".markdown", ".mdown", ".mkdn", ".mkd":
433
433
htmlString = p.rctx.RenderMarkdown(params.Readme)
434
434
params.Raw = false
435
-
params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString))
435
+
params.HTMLReadme = template.HTML(p.rctx.Sanitize(htmlString))
436
436
default:
437
437
htmlString = string(params.Readme)
438
438
params.Raw = true
···
562
562
case markup.FormatMarkdown:
563
563
p.rctx.RepoInfo = params.RepoInfo
564
564
p.rctx.RendererType = markup.RendererTypeRepoMarkdown
565
-
params.RenderedContents = template.HTML(bluemonday.UGCPolicy().Sanitize(p.rctx.RenderMarkdown(params.Contents)))
565
+
htmlString := p.rctx.RenderMarkdown(params.Contents)
566
+
params.RenderedContents = template.HTML(p.rctx.Sanitize(htmlString))
566
567
}
567
568
}
568
569
+1
appview/pages/repoinfo/repoinfo.go
+1
appview/pages/repoinfo/repoinfo.go
+2
appview/state/repo.go
+2
appview/state/repo.go
···
946
946
Description string
947
947
CreatedAt string
948
948
Ref string
949
+
CurrentDir string
949
950
}
950
951
951
952
func (f *FullyResolvedRepo) OwnerDid() string {
···
1104
1105
PullCount: pullCount,
1105
1106
},
1106
1107
DisableFork: disableFork,
1108
+
CurrentDir: f.CurrentDir,
1107
1109
}
1108
1110
1109
1111
if sourceRepo != nil {
+31
appview/state/repo_util.go
+31
appview/state/repo_util.go
···
7
7
"log"
8
8
"math/big"
9
9
"net/http"
10
+
"net/url"
11
+
"path"
12
+
"strings"
10
13
11
14
"github.com/bluesky-social/indigo/atproto/identity"
12
15
"github.com/bluesky-social/indigo/atproto/syntax"
···
59
62
ref = defaultBranch.Branch
60
63
}
61
64
65
+
currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath(), ref))
66
+
62
67
// pass through values from the middleware
63
68
description, ok := r.Context().Value("repoDescription").(string)
64
69
addedAt, ok := r.Context().Value("repoAddedAt").(string)
···
71
76
Description: description,
72
77
CreatedAt: addedAt,
73
78
Ref: ref,
79
+
CurrentDir: currentDir,
74
80
}, nil
75
81
}
76
82
···
81
87
} else {
82
88
return repoinfo.RolesInRepo{}
83
89
}
90
+
}
91
+
92
+
// extractPathAfterRef gets the actual repository path
93
+
// after the ref. for example:
94
+
//
95
+
// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/
96
+
func extractPathAfterRef(fullPath, ref string) string {
97
+
fullPath = strings.TrimPrefix(fullPath, "/")
98
+
99
+
ref = url.PathEscape(ref)
100
+
101
+
prefixes := []string{
102
+
fmt.Sprintf("blob/%s/", ref),
103
+
fmt.Sprintf("tree/%s/", ref),
104
+
fmt.Sprintf("raw/%s/", ref),
105
+
}
106
+
107
+
for _, prefix := range prefixes {
108
+
idx := strings.Index(fullPath, prefix)
109
+
if idx != -1 {
110
+
return fullPath[idx+len(prefix):]
111
+
}
112
+
}
113
+
114
+
return ""
84
115
}
85
116
86
117
func uniqueEmails(commits []*object.Commit) []string {