diff --git a/modules/markup/external/frontend.go b/modules/markup/external/frontend.go index 3f7c26c5751..2a5d652a75b 100644 --- a/modules/markup/external/frontend.go +++ b/modules/markup/external/frontend.go @@ -21,19 +21,12 @@ type frontendRenderer struct { patterns []string } -var ( - _ markup.PostProcessRenderer = (*frontendRenderer)(nil) - _ markup.ExternalRenderer = (*frontendRenderer)(nil) -) +var _ markup.ExternalRenderer = (*frontendRenderer)(nil) func (p *frontendRenderer) Name() string { return p.name } -func (p *frontendRenderer) NeedPostProcess() bool { - return false -} - func (p *frontendRenderer) FileNamePatterns() []string { // TODO: the file extensions are ambiguous, even if the file name matches, it doesn't mean that the file is a 3D model // There are some approaches to make it more accurate, but they are all complicated: diff --git a/modules/markup/jupyter/jupyter.go b/modules/markup/jupyter/jupyter.go index 8acaf64d0d1..d9f9740a9b3 100644 --- a/modules/markup/jupyter/jupyter.go +++ b/modules/markup/jupyter/jupyter.go @@ -29,9 +29,8 @@ func init() { type renderer struct{} var ( - _ markup.Renderer = (*renderer)(nil) - _ markup.PostProcessRenderer = (*renderer)(nil) - _ markup.ExternalRenderer = (*renderer)(nil) // FIXME: this is not an external render, need to refactor the framework in the future + _ markup.Renderer = (*renderer)(nil) + _ markup.ExternalRenderer = (*renderer)(nil) // FIXME: this is not an external render, need to refactor the framework in the future ) type mimeHandler struct { @@ -96,8 +95,6 @@ func (renderer) Name() string { return "jupyter-render" } -func (renderer) NeedPostProcess() bool { return true } - func (renderer) GetExternalRendererOptions() markup.ExternalRendererOptions { return markup.ExternalRendererOptions{ // HINT: no need to let markup render sanitize the output because there are many special CSS class names, inline attributes. diff --git a/modules/markup/jupyter/jupyter_test.go b/modules/markup/jupyter/jupyter_test.go index bdea55308cf..1fc987e9d5c 100644 --- a/modules/markup/jupyter/jupyter_test.go +++ b/modules/markup/jupyter/jupyter_test.go @@ -274,7 +274,7 @@ func TestIntegrationAndSanitization(t *testing.T) { "execution_count": 1, "data": { "text/html": [ - "
Safe Content
" + "
[[name=no-post-process|link=/link]]
" ] }, "metadata": {} @@ -304,7 +304,7 @@ func TestIntegrationAndSanitization(t *testing.T) {
Out [1]:
-
Safe Content
+
[[name=no-post-process|link=/link]]
diff --git a/modules/markup/main_test.go b/modules/markup/main_test.go index f8a77ad86ef..8924e43de33 100644 --- a/modules/markup/main_test.go +++ b/modules/markup/main_test.go @@ -14,7 +14,6 @@ import ( func TestMain(m *testing.M) { setting.IsInTesting = true markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true - setting.Markdown.FileNamePatterns = []string{"*.md"} markup.RefreshFileNamePatterns() os.Exit(m.Run()) } diff --git a/modules/setting/markup.go b/modules/setting/markup.go index 39c59025de2..fa2d9367fd4 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -50,7 +50,8 @@ var Markdown = struct { MathCodeBlockDetection []string MathCodeBlockOptions MarkdownMathCodeBlockOptions `ini:"-"` }{ - EnableMath: true, + EnableMath: true, + FileNamePatterns: []string{"*.md"}, } // MarkupRenderer defines the external parser configured in ini diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go index 8d3cb962842..43aab53d32b 100644 --- a/routers/api/v1/misc/markup.go +++ b/routers/api/v1/misc/markup.go @@ -4,8 +4,9 @@ package misc import ( - "gitea.dev/modules/markup" - "gitea.dev/modules/markup/markdown" + "io" + + "gitea.dev/modules/setting" api "gitea.dev/modules/structs" "gitea.dev/modules/util" "gitea.dev/modules/web" @@ -84,9 +85,6 @@ func MarkdownRaw(ctx *context.APIContext) { // "$ref": "#/responses/MarkdownRender" // "422": // "$ref": "#/responses/validationError" - defer ctx.Req.Body.Close() - if err := markdown.RenderRaw(markup.NewRenderContext(ctx), ctx.Req.Body, ctx.Resp); err != nil { - ctx.APIErrorInternal(err) - return - } + textBytes, _ := io.ReadAll(io.LimitReader(ctx.Req.Body, setting.UI.MaxDisplayFileSize)) + common.RenderMarkup(ctx.Base, ctx.Repo, "markdown", util.UnsafeBytesToString(textBytes), "", "") } diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index bf02d0a9597..d8b83ea330f 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -7,19 +7,15 @@ import ( go_context "context" "io" "net/http" - "os" "path" "strings" "testing" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" "gitea.dev/modules/markup" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" "gitea.dev/modules/test" "gitea.dev/modules/web" - context_service "gitea.dev/services/context" "gitea.dev/services/contexttest" "github.com/stretchr/testify/assert" @@ -27,13 +23,6 @@ import ( const AppURL = "http://localhost:3000/" -func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - FixtureFiles: []string{"repository.yml", "user.yml"}, - }) - os.Exit(m.Run()) -} - func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) { setting.AppURL = AppURL defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() @@ -49,13 +38,11 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe FilePath: filePath, } ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup") - ctx.Repo = &context_service.Repository{} - ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) web.SetForm(ctx, &options) Markup(ctx) assert.Equal(t, expectedBody, resp.Body.String()) assert.Equal(t, expectedCode, resp.Code) - resp.Body.Reset() + assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "script-src * 'nonce-") } func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) { @@ -76,11 +63,10 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody Markdown(ctx) assert.Equal(t, responseBody, resp.Body.String()) assert.Equal(t, responseCode, resp.Code) - resp.Body.Reset() + assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "script-src * 'nonce-") } func TestAPI_RenderGFM(t *testing.T) { - unittest.PrepareTestEnv(t) markup.Init(&markup.RenderHelperFuncs{ IsUsernameMentionable: func(ctx go_context.Context, username string) bool { return username == "r-lyeh" diff --git a/routers/common/errpage.go b/routers/common/errpage.go index 1426b05ef93..6152a0c05ce 100644 --- a/routers/common/errpage.go +++ b/routers/common/errpage.go @@ -40,6 +40,7 @@ func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode in if acceptsHTML { err := templates.PageRenderer().HTML(outBuf, respCode, tmpl, ctxData, tmplCtx) if err != nil { + log.Error("Failed to render error page template %s: %v", tmpl, err) _, _ = w.Write([]byte("Internal server error but failed to render error page template, please collect error logs and report to Gitea issue tracker")) return } diff --git a/routers/common/markup.go b/routers/common/markup.go index 3b626c7031f..a379deda3ea 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -31,6 +31,8 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur // for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md" // and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc" + ctx.SetHeaderContentSecurityPolicyGeneral() + if mode == "" || mode == "markdown" { // raw Markdown doesn't do any special handling // TODO: raw markdown doesn't do any link processing, so "urlPathContext" doesn't take effect diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index b323da163c0..ef3d3173c36 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -64,6 +64,9 @@ func RenderFile(ctx *context.Context) { extRendererOpts := extRenderer.GetExternalRendererOptions() if extRendererOpts.ContentSandbox != "" { ctx.Resp.Header().Add("Content-Security-Policy", "sandbox "+extRendererOpts.ContentSandbox) + } else { + // if no sandbox, just apply the same CSP as a general Gitea web page + ctx.SetHeaderContentSecurityPolicyGeneral() } err = markup.RenderWithRenderer(rctx, renderer, rendererInput, ctx.Resp) diff --git a/services/context/base.go b/services/context/base.go index 17b464e1c46..50274ec1549 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -188,6 +188,26 @@ func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML { return b.Locale.TrN(cnt, key1, keyN, args...) } +func CspScriptNonce(ctx reqctx.RequestContext) (ret string) { + // Generate a random nonce for each request and cache it in the context to make it usable during the whole rendering process. + // + // Some "`, respSub.Body.String(), ) - assert.Empty(t, respSub.Header().Get("Content-Security-Policy")) + assert.NotContains(t, respSub.Header().Get("Content-Security-Policy"), "sandbox") + assert.Contains(t, respSub.Header().Get("Content-Security-Policy"), "nonce-") }) }) })