Skip to content

Commit 256afbc

Browse files
committed
split renderer
1 parent c76926d commit 256afbc

File tree

3 files changed

+199
-186
lines changed

3 files changed

+199
-186
lines changed

Diff for: modules/markup/render.go

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package markup
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"io"
11+
"net/url"
12+
"path"
13+
"strings"
14+
"sync"
15+
16+
"code.gitea.io/gitea/modules/git"
17+
"code.gitea.io/gitea/modules/gitrepo"
18+
"code.gitea.io/gitea/modules/setting"
19+
"code.gitea.io/gitea/modules/util"
20+
21+
"github.com/yuin/goldmark/ast"
22+
)
23+
24+
// RenderContext represents a render context
25+
type RenderContext struct {
26+
Ctx context.Context
27+
RelativePath string // relative path from tree root of the branch
28+
Type string
29+
IsWiki bool
30+
Links Links
31+
Metas map[string]string // user, repo, mode(comment/document)
32+
DefaultLink string
33+
GitRepo *git.Repository
34+
Repo gitrepo.Repository
35+
ShaExistCache map[string]bool
36+
cancelFn func()
37+
SidebarTocNode ast.Node
38+
RenderMetaAs RenderMetaMode
39+
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
40+
}
41+
42+
// Cancel runs any cleanup functions that have been registered for this Ctx
43+
func (ctx *RenderContext) Cancel() {
44+
if ctx == nil {
45+
return
46+
}
47+
ctx.ShaExistCache = map[string]bool{}
48+
if ctx.cancelFn == nil {
49+
return
50+
}
51+
ctx.cancelFn()
52+
}
53+
54+
// AddCancel adds the provided fn as a Cleanup for this Ctx
55+
func (ctx *RenderContext) AddCancel(fn func()) {
56+
if ctx == nil {
57+
return
58+
}
59+
oldCancelFn := ctx.cancelFn
60+
if oldCancelFn == nil {
61+
ctx.cancelFn = fn
62+
return
63+
}
64+
ctx.cancelFn = func() {
65+
defer oldCancelFn()
66+
fn()
67+
}
68+
}
69+
70+
// Render renders markup file to HTML with all specific handling stuff.
71+
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
72+
if ctx.Type != "" {
73+
return renderByType(ctx, input, output)
74+
} else if ctx.RelativePath != "" {
75+
return renderFile(ctx, input, output)
76+
}
77+
return errors.New("render options both filename and type missing")
78+
}
79+
80+
// RenderString renders Markup string to HTML with all specific handling stuff and return string
81+
func RenderString(ctx *RenderContext, content string) (string, error) {
82+
var buf strings.Builder
83+
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
84+
return "", err
85+
}
86+
return buf.String(), nil
87+
}
88+
89+
func renderIFrame(ctx *RenderContext, output io.Writer) error {
90+
// set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight)
91+
// at the moment, only "allow-scripts" is allowed for sandbox mode.
92+
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
93+
// TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read
94+
_, err := io.WriteString(output, fmt.Sprintf(`
95+
<iframe src="%s/%s/%s/render/%s/%s"
96+
name="giteaExternalRender"
97+
onload="this.height=giteaExternalRender.document.documentElement.scrollHeight"
98+
width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden"
99+
sandbox="allow-scripts"
100+
></iframe>`,
101+
setting.AppSubURL,
102+
url.PathEscape(ctx.Metas["user"]),
103+
url.PathEscape(ctx.Metas["repo"]),
104+
ctx.Metas["BranchNameSubURL"],
105+
url.PathEscape(ctx.RelativePath),
106+
))
107+
return err
108+
}
109+
110+
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
111+
var wg sync.WaitGroup
112+
var err error
113+
pr, pw := io.Pipe()
114+
defer func() {
115+
_ = pr.Close()
116+
_ = pw.Close()
117+
}()
118+
119+
var pr2 io.ReadCloser
120+
var pw2 io.WriteCloser
121+
122+
var sanitizerDisabled bool
123+
if r, ok := renderer.(ExternalRenderer); ok {
124+
sanitizerDisabled = r.SanitizerDisabled()
125+
}
126+
127+
if !sanitizerDisabled {
128+
pr2, pw2 = io.Pipe()
129+
defer func() {
130+
_ = pr2.Close()
131+
_ = pw2.Close()
132+
}()
133+
134+
wg.Add(1)
135+
go func() {
136+
err = SanitizeReader(pr2, renderer.Name(), output)
137+
_ = pr2.Close()
138+
wg.Done()
139+
}()
140+
} else {
141+
pw2 = util.NopCloser{Writer: output}
142+
}
143+
144+
wg.Add(1)
145+
go func() {
146+
if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() {
147+
err = PostProcess(ctx, pr, pw2)
148+
} else {
149+
_, err = io.Copy(pw2, pr)
150+
}
151+
_ = pr.Close()
152+
_ = pw2.Close()
153+
wg.Done()
154+
}()
155+
156+
if err1 := renderer.Render(ctx, input, pw); err1 != nil {
157+
return err1
158+
}
159+
_ = pw.Close()
160+
161+
wg.Wait()
162+
return err
163+
}
164+
165+
func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error {
166+
if renderer, ok := renderers[ctx.Type]; ok {
167+
return render(ctx, renderer, input, output)
168+
}
169+
return fmt.Errorf("unsupported render type: %s", ctx.Type)
170+
}
171+
172+
// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render
173+
type ErrUnsupportedRenderExtension struct {
174+
Extension string
175+
}
176+
177+
func IsErrUnsupportedRenderExtension(err error) bool {
178+
_, ok := err.(ErrUnsupportedRenderExtension)
179+
return ok
180+
}
181+
182+
func (err ErrUnsupportedRenderExtension) Error() string {
183+
return fmt.Sprintf("unsupported render extension: %s", err.Extension)
184+
}
185+
186+
func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error {
187+
extension := strings.ToLower(path.Ext(ctx.RelativePath))
188+
if renderer, ok := extRenderers[extension]; ok {
189+
if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
190+
if !ctx.InStandalonePage {
191+
// for an external render, it could only output its content in a standalone page
192+
// otherwise, a <iframe> should be outputted to embed the external rendered page
193+
return renderIFrame(ctx, output)
194+
}
195+
}
196+
return render(ctx, renderer, input, output)
197+
}
198+
return ErrUnsupportedRenderExtension{extension}
199+
}
File renamed without changes.

0 commit comments

Comments
 (0)