Skip to content

Commit 2d59368

Browse files
committed
Add new RENDER_CONTENT_MODE value iframe-allow-same-origin to allow load html
1 parent db3355c commit 2d59368

File tree

5 files changed

+90
-57
lines changed

5 files changed

+90
-57
lines changed

custom/conf/app.example.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,7 @@ PATH =
21742174
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
21752175
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
21762176
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
2177+
;; * iframe-allow-same-origin: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin enabled so don't use this except you know what it means.
21772178
;RENDER_CONTENT_MODE=sanitized
21782179

21792180
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

modules/markup/external/external.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,21 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
6161

6262
// SanitizerDisabled disabled sanitize if return true
6363
func (p *Renderer) SanitizerDisabled() bool {
64-
return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
64+
return p.RenderContentMode == setting.RenderContentModeNoSanitizer ||
65+
p.RenderContentMode == setting.RenderContentModeIframe ||
66+
p.RenderContentMode == setting.RenderContentModeIframeAllowSameOrigin
6567
}
6668

6769
// DisplayInIFrame represents whether render the content with an iframe
6870
func (p *Renderer) DisplayInIFrame() bool {
6971
return p.RenderContentMode == setting.RenderContentModeIframe
7072
}
7173

74+
// AllowSameOrigin represents whether render allow same origin
75+
func (p *Renderer) AllowSameOrigin() bool {
76+
return p.RenderContentMode == setting.RenderContentModeIframeAllowSameOrigin
77+
}
78+
7279
func envMark(envName string) string {
7380
if runtime.GOOS == "windows" {
7481
return "%" + envName + "%"

modules/markup/renderer.go

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,17 @@ type Header struct {
4444

4545
// RenderContext represents a render context
4646
type RenderContext struct {
47-
Ctx context.Context
48-
RelativePath string // relative path from tree root of the branch
49-
Type string
50-
IsWiki bool
51-
URLPrefix string
52-
Metas map[string]string
53-
DefaultLink string
54-
GitRepo *git.Repository
55-
ShaExistCache map[string]bool
56-
cancelFn func()
57-
TableOfContents []Header
58-
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
47+
Ctx context.Context
48+
RelativePath string // relative path from tree root of the branch
49+
Type string
50+
IsWiki bool
51+
URLPrefix string
52+
Metas map[string]string
53+
DefaultLink string
54+
GitRepo *git.Repository
55+
ShaExistCache map[string]bool
56+
cancelFn func()
57+
TableOfContents []Header
5958
}
6059

6160
// Cancel runs any cleanup functions that have been registered for this Ctx
@@ -106,6 +105,9 @@ type ExternalRenderer interface {
106105

107106
// DisplayInIFrame represents whether render the content with an iframe
108107
DisplayInIFrame() bool
108+
109+
// AllowSameOrigin represents whether render allow same origin
110+
AllowSameOrigin() bool
109111
}
110112

111113
// RendererContentDetector detects if the content can be rendered
@@ -152,14 +154,39 @@ func DetectRendererType(filename string, input io.Reader) string {
152154
return ""
153155
}
154156

157+
// GetRenderer returned the renderer according type or relativepath
158+
func GetRenderer(tp, relativePath string) (Renderer, error) {
159+
if tp != "" {
160+
if renderer, ok := renderers[tp]; ok {
161+
return renderer, nil
162+
}
163+
return nil, ErrUnsupportedRenderType{tp}
164+
}
165+
166+
if relativePath != "" {
167+
extension := strings.ToLower(filepath.Ext(relativePath))
168+
if renderer, ok := extRenderers[extension]; ok {
169+
return renderer, nil
170+
}
171+
return nil, ErrUnsupportedRenderExtension{extension}
172+
}
173+
return nil, errors.New("Render options both filename and type missing")
174+
}
175+
155176
// Render renders markup file to HTML with all specific handling stuff.
156177
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
157-
if ctx.Type != "" {
158-
return renderByType(ctx, input, output)
159-
} else if ctx.RelativePath != "" {
160-
return renderFile(ctx, input, output)
178+
renderer, err := GetRenderer(ctx.Type, ctx.RelativePath)
179+
if err != nil {
180+
return err
181+
}
182+
183+
if r, ok := renderer.(ExternalRenderer); ok && (r.DisplayInIFrame() || r.AllowSameOrigin()) {
184+
// for an external render, it could only output its content in a standalone page
185+
// otherwise, a <iframe> should be outputted to embed the external rendered page
186+
return renderIFrame(ctx, output, r.AllowSameOrigin())
161187
}
162-
return errors.New("Render options both filename and type missing")
188+
189+
return RenderDirect(ctx, renderer, input, output)
163190
}
164191

165192
// RenderString renders Markup string to HTML with all specific handling stuff and return string
@@ -177,7 +204,11 @@ type nopCloser struct {
177204

178205
func (nopCloser) Close() error { return nil }
179206

180-
func renderIFrame(ctx *RenderContext, output io.Writer) error {
207+
func renderIFrame(ctx *RenderContext, output io.Writer, allowSameOrigin bool) error {
208+
var allowSameOriginStr string
209+
if allowSameOrigin {
210+
allowSameOriginStr = " allow-same-origin"
211+
}
181212
// set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight)
182213
// at the moment, only "allow-scripts" is allowed for sandbox mode.
183214
// "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
@@ -187,18 +218,20 @@ func renderIFrame(ctx *RenderContext, output io.Writer) error {
187218
name="giteaExternalRender"
188219
onload="this.height=giteaExternalRender.document.documentElement.scrollHeight"
189220
width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden"
190-
sandbox="allow-scripts"
221+
sandbox="allow-scripts%s"
191222
></iframe>`,
192223
setting.AppSubURL,
193224
url.PathEscape(ctx.Metas["user"]),
194225
url.PathEscape(ctx.Metas["repo"]),
195226
ctx.Metas["BranchNameSubURL"],
196227
url.PathEscape(ctx.RelativePath),
228+
allowSameOriginStr,
197229
))
198230
return err
199231
}
200232

201-
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
233+
// RenderDirect renders markup file to HTML with all specific handling stuff.
234+
func RenderDirect(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
202235
var wg sync.WaitGroup
203236
var err error
204237
pr, pw := io.Pipe()
@@ -262,13 +295,6 @@ func (err ErrUnsupportedRenderType) Error() string {
262295
return fmt.Sprintf("Unsupported render type: %s", err.Type)
263296
}
264297

265-
func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error {
266-
if renderer, ok := renderers[ctx.Type]; ok {
267-
return render(ctx, renderer, input, output)
268-
}
269-
return ErrUnsupportedRenderType{ctx.Type}
270-
}
271-
272298
// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render
273299
type ErrUnsupportedRenderExtension struct {
274300
Extension string
@@ -278,21 +304,6 @@ func (err ErrUnsupportedRenderExtension) Error() string {
278304
return fmt.Sprintf("Unsupported render extension: %s", err.Extension)
279305
}
280306

281-
func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error {
282-
extension := strings.ToLower(filepath.Ext(ctx.RelativePath))
283-
if renderer, ok := extRenderers[extension]; ok {
284-
if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
285-
if !ctx.InStandalonePage {
286-
// for an external render, it could only output its content in a standalone page
287-
// otherwise, a <iframe> should be outputted to embed the external rendered page
288-
return renderIFrame(ctx, output)
289-
}
290-
}
291-
return render(ctx, renderer, input, output)
292-
}
293-
return ErrUnsupportedRenderExtension{extension}
294-
}
295-
296307
// Type returns if markup format via the filename
297308
func Type(filename string) string {
298309
if parser := GetRendererByFileName(filename); parser != nil {

modules/setting/markup.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ var (
2121
)
2222

2323
const (
24-
RenderContentModeSanitized = "sanitized"
25-
RenderContentModeNoSanitizer = "no-sanitizer"
26-
RenderContentModeIframe = "iframe"
24+
RenderContentModeSanitized = "sanitized"
25+
RenderContentModeNoSanitizer = "no-sanitizer"
26+
RenderContentModeIframe = "iframe"
27+
RenderContentModeIframeAllowSameOrigin = "iframe-allow-same-origin"
2728
)
2829

2930
// MarkupRenderer defines the external parser configured in ini
@@ -160,7 +161,8 @@ func newMarkupRenderer(name string, sec *ini.Section) {
160161
}
161162
if renderContentMode != RenderContentModeSanitized &&
162163
renderContentMode != RenderContentModeNoSanitizer &&
163-
renderContentMode != RenderContentModeIframe {
164+
renderContentMode != RenderContentModeIframe &&
165+
renderContentMode != RenderContentModeIframeAllowSameOrigin {
164166
log.Error("invalid RENDER_CONTENT_MODE: %q, default to %q", renderContentMode, RenderContentModeSanitized)
165167
renderContentMode = RenderContentModeSanitized
166168
}

routers/web/repo/render.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package repo
66

77
import (
88
"bytes"
9+
"fmt"
910
"io"
1011
"net/http"
1112
"path"
@@ -63,17 +64,28 @@ func RenderFile(ctx *context.Context) {
6364
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
6465
}
6566

66-
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
67-
err = markup.Render(&markup.RenderContext{
68-
Ctx: ctx,
69-
RelativePath: ctx.Repo.TreePath,
70-
URLPrefix: path.Dir(treeLink),
71-
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
72-
GitRepo: ctx.Repo.GitRepo,
73-
InStandalonePage: true,
74-
}, rd, ctx.Resp)
67+
var allowSameOriginStr string
68+
69+
renderer, err := markup.GetRenderer("", ctx.Repo.TreePath)
7570
if err != nil {
76-
ctx.ServerError("Render", err)
71+
ctx.ServerError("GetRenderer", err)
72+
return
73+
}
74+
75+
if r, ok := renderer.(markup.ExternalRenderer); ok && r.AllowSameOrigin() {
76+
allowSameOriginStr = " allow-same-origin"
77+
}
78+
79+
ctx.Resp.Header().Add("Content-Security-Policy", fmt.Sprintf("frame-src 'self'; sandbox%s allow-scripts", allowSameOriginStr))
80+
81+
if err = markup.RenderDirect(&markup.RenderContext{
82+
Ctx: ctx,
83+
RelativePath: ctx.Repo.TreePath,
84+
URLPrefix: path.Dir(treeLink),
85+
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
86+
GitRepo: ctx.Repo.GitRepo,
87+
}, renderer, rd, ctx.Resp); err != nil {
88+
ctx.ServerError("RenderDirect", err)
7789
return
7890
}
7991
}

0 commit comments

Comments
 (0)