Files
forge/tests/integration/markup_external_test.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

147 lines
6.8 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"strings"
"testing"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/models/unittest"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/modules/charset"
"github.com/hanzoai/git/modules/markup"
"github.com/hanzoai/git/modules/markup/external"
"github.com/hanzoai/git/modules/public"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/test"
"github.com/hanzoai/git/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExternalMarkupRenderer(t *testing.T) {
defer tests.PrepareTestEnv(t)()
if !setting.Database.Type.IsSQLite3() {
t.Skip("only SQLite3 test config supports external markup renderer")
return
}
const binaryContentPrefix = "any prefix text."
const binaryContent = binaryContentPrefix + "\xfe\xfe\xfe\x00\xff\xff"
detectedEncoding, _ := charset.DetectEncoding([]byte(binaryContent))
assert.NotEqual(t, binaryContent, strings.ToValidUTF8(binaryContent, "?"))
assert.Equal(t, "ISO-8859-2", detectedEncoding) // even if the binary content can be detected as text encoding, it shouldn't affect the raw rendering
onGitRun(t, func(t *testing.T, _ *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
_, err := createFileInBranch(user2, repo1, createFileInBranchOptions{}, map[string]string{
"test.html": `<div><any attr="val"><script></script></div>`,
"html.no-sanitizer": `<script>foo("raw")</script>`,
"bin.no-sanitizer": binaryContent,
})
require.NoError(t, err)
t.Run("RenderNoSanitizer", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/html.no-sanitizer")
resp := MakeRequest(t, req, http.StatusOK)
div := NewHTMLParser(t, resp.Body).Find("div.file-view")
data, err := div.Html()
assert.NoError(t, err)
assert.Equal(t, `<script>foo("raw")</script>`, strings.TrimSpace(data))
req = NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer")
resp = MakeRequest(t, req, http.StatusOK)
div = NewHTMLParser(t, resp.Body).Find("div.file-view")
data, err = div.Html()
assert.NoError(t, err)
assert.Equal(t, strings.ReplaceAll(binaryContent, "\x00", ""), strings.TrimSpace(data)) // HTML template engine removes the null bytes
})
})
t.Run("RenderContentDirectly", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html")
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
doc := NewHTMLParser(t, resp.Body)
div := doc.Find("div.file-view")
data, err := div.Html()
assert.NoError(t, err)
// the content is fully sanitized
assert.Equal(t, `<div>&lt;script&gt;&lt;/script&gt;</div>`, strings.TrimSpace(data))
})
// above tested in-page rendering (no iframe), then we test iframe mode below
r := markup.DetectRendererTypeByFilename("any-file.html").(*external.Renderer)
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
assert.True(t, r.NeedPostProcess())
r = markup.DetectRendererTypeByFilename("any-file.no-sanitizer").(*external.Renderer)
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
assert.False(t, r.NeedPostProcess())
t.Run("RenderContentInIFrame", func(t *testing.T) {
t.Run("DefaultSandbox", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html")
t.Run("ParentPage", func(t *testing.T) {
respParent := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "text/html; charset=utf-8", respParent.Header().Get("Content-Type"))
iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe")
assert.Empty(t, iframe.AttrOr("src", "")) // src should be empty, "data-src" is used instead
// no sandbox on parent page because the rendered response should always have correct sandbox
assert.Equal(t, "(non-existing)", iframe.AttrOr("sandbox", "(non-existing)"))
assert.Equal(t, "/user2/repo1/render/branch/master/test.html", iframe.AttrOr("data-src", ""))
})
t.Run("FramePage", func(t *testing.T) {
req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/test.html")
respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "text/html; charset=utf-8", respSub.Header().Get("Content-Type"))
// default sandbox in sub-page response (there should be no "allow-same-origin")
assert.Equal(t, "sandbox allow-scripts allow-forms allow-modals allow-popups allow-downloads", respSub.Header().Get("Content-Security-Policy"))
// FIXME: actually here is a bug (legacy design problem), the "PostProcess" will escape "<script>" tag, but it indeed is the sanitizer's job
assert.Equal(t,
`<script nonce crossorigin src="`+public.AssetURI("web_src/js/external-render-helper.ts")+`" id="hanzo-external-render-helper" data-render-query-string=""></script>`+
`<link rel="stylesheet" href="`+public.AssetURI("web_src/css/themes/theme-hanzo-auto.css")+`">`+
`<div><any attr="val">&lt;script&gt;&lt;/script&gt;</any></div>`,
respSub.Body.String(),
)
})
})
t.Run("NoSanitizerNoSandbox", func(t *testing.T) {
t.Run("BinaryContent", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer")
respParent := MakeRequest(t, req, http.StatusOK)
iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe")
assert.Equal(t, "/user2/repo1/render/branch/master/bin.no-sanitizer", iframe.AttrOr("data-src", ""))
req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/bin.no-sanitizer")
respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, binaryContent, respSub.Body.String()) // raw content should keep the raw bytes (including invalid UTF-8 bytes), and no "external-render-iframe" helpers
assert.Empty(t, respSub.Header().Get("Content-Security-Policy"), "sandbox is disabled by RENDER_CONTENT_SANDBOX")
})
t.Run("HTMLContentWithExternalRenderIframeHelper", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/render/branch/master/html.no-sanitizer?a=1%2f2")
respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t,
`<script nonce crossorigin src="`+public.AssetURI("web_src/js/external-render-helper.ts")+`" id="hanzo-external-render-helper" data-render-query-string="a=1%2f2"></script>`+
`<link rel="stylesheet" href="`+public.AssetURI("web_src/css/themes/theme-hanzo-auto.css")+`">`+
`<script>foo("raw")</script>`,
respSub.Body.String(),
)
assert.Empty(t, respSub.Header().Get("Content-Security-Policy"))
})
})
})
}