mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
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]>
147 lines
6.8 KiB
Go
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><script></script></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"><script></script></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"))
|
|
})
|
|
})
|
|
})
|
|
}
|