Skip to content

Commit bb3d67f

Browse files
authored
Merge branch 'main' into private-reusable-workflow
2 parents 3ec3d60 + 9a73a1f commit bb3d67f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1445
-1147
lines changed

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ linters:
153153
text: '(?i)exitAfterDefer:'
154154
paths:
155155
- node_modules
156+
- .venv
156157
- public
157158
- web_src
158159
- third_party$
@@ -172,6 +173,7 @@ formatters:
172173
generated: lax
173174
paths:
174175
- node_modules
176+
- .venv
175177
- public
176178
- web_src
177179
- third_party$

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ XGO_VERSION := go-1.25.x
3131

3232
AIR_PACKAGE ?= github.com/air-verse/air@v1
3333
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
34-
GOFUMPT_PACKAGE ?= mvdan.cc/[email protected].1
35-
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
34+
GOFUMPT_PACKAGE ?= mvdan.cc/[email protected].2
35+
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0
3636
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/[email protected]
3737
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/[email protected]
38-
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286
38+
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
3939
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
4040
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
4141
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
@@ -258,7 +258,7 @@ clean: ## delete backend and integration files
258258

259259
.PHONY: fmt
260260
fmt: ## format the Go and template code
261-
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
261+
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run tools/code-batch-process.go gitea-fmt -w '{file-list}'
262262
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
263263
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
264264
@# whitespace before it
@@ -472,7 +472,7 @@ test\#%:
472472
coverage:
473473
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
474474
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
475-
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
475+
$(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
476476

477477
.PHONY: unit-test-coverage
478478
unit-test-coverage:

custom/conf/app.example.ini

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2540,7 +2540,19 @@ LEVEL = Info
25402540
;; * 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.*] .
25412541
;; * 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.
25422542
;; * 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.
2543-
;RENDER_CONTENT_MODE=sanitized
2543+
;RENDER_CONTENT_MODE = sanitized
2544+
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
2545+
;; It defaults to a safe set of "allow-*" restrictions (space separated).
2546+
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
2547+
;; When set it, make sure there is no security risk:
2548+
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
2549+
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
2550+
;RENDER_CONTENT_SANDBOX =
2551+
;; Whether post-process the rendered HTML content, including:
2552+
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
2553+
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
2554+
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
2555+
;NEED_POST_PROCESS = false
25442556

25452557
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25462558
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

eslint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export default defineConfig([
4949
},
5050
linterOptions: {
5151
reportUnusedDisableDirectives: 2,
52+
reportUnusedInlineConfigs: 2,
5253
},
5354
plugins: {
5455
'@eslint-community/eslint-comments': comments,

modules/git/notes_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,5 @@ func TestGetNonExistentNotes(t *testing.T) {
4747
note := Note{}
4848
err = GetNote(t.Context(), bareRepo1, "non_existent_sha", &note)
4949
assert.Error(t, err)
50-
assert.IsType(t, ErrNotExist{}, err)
50+
assert.ErrorAs(t, err, &ErrNotExist{})
5151
}

modules/httplib/serve.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byt
126126
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
127127
// should generally be safe as scripts inside PDF can not escape the PDF document
128128
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
129+
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
129130
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
130131
}
131132

modules/markup/external/external.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"code.gitea.io/gitea/modules/markup"
1616
"code.gitea.io/gitea/modules/process"
1717
"code.gitea.io/gitea/modules/setting"
18+
19+
"github.com/kballard/go-shellquote"
1820
)
1921

2022
// RegisterRenderers registers all supported third part renderers according settings
@@ -56,14 +58,11 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
5658
return p.MarkupSanitizerRules
5759
}
5860

59-
// SanitizerDisabled disabled sanitize if return true
60-
func (p *Renderer) SanitizerDisabled() bool {
61-
return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
62-
}
63-
64-
// DisplayInIFrame represents whether render the content with an iframe
65-
func (p *Renderer) DisplayInIFrame() bool {
66-
return p.RenderContentMode == setting.RenderContentModeIframe
61+
func (p *Renderer) GetExternalRendererOptions() (ret markup.ExternalRendererOptions) {
62+
ret.SanitizerDisabled = p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
63+
ret.DisplayInIframe = p.RenderContentMode == setting.RenderContentModeIframe
64+
ret.ContentSandbox = p.RenderContentSandbox
65+
return ret
6766
}
6867

6968
func envMark(envName string) string {
@@ -81,7 +80,10 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
8180
envMark("GITEA_PREFIX_SRC"), baseLinkSrc,
8281
envMark("GITEA_PREFIX_RAW"), baseLinkRaw,
8382
).Replace(p.Command)
84-
commands := strings.Fields(command)
83+
commands, err := shellquote.Split(command)
84+
if err != nil || len(commands) == 0 {
85+
return fmt.Errorf("%s invalid command %q: %w", p.Name(), p.Command, err)
86+
}
8587
args := commands[1:]
8688

8789
if p.IsInputFile {

modules/markup/internal/finalprocessor.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package internal
55

66
import (
77
"bytes"
8+
"html/template"
89
"io"
910
)
1011

1112
type finalProcessor struct {
1213
renderInternal *RenderInternal
14+
extraHeadHTML template.HTML
1315

1416
output io.Writer
1517
buf bytes.Buffer
@@ -25,6 +27,32 @@ func (p *finalProcessor) Close() error {
2527
// because "postProcess" already does so. In the future we could optimize the code to process data on the fly.
2628
buf := p.buf.Bytes()
2729
buf = bytes.ReplaceAll(buf, []byte(` data-attr-class="`+p.renderInternal.secureIDPrefix), []byte(` class="`))
28-
_, err := p.output.Write(buf)
30+
31+
tmp := bytes.TrimSpace(buf)
32+
isLikelyHTML := len(tmp) != 0 && tmp[0] == '<' && tmp[len(tmp)-1] == '>' && bytes.Index(tmp, []byte(`</`)) > 0
33+
if !isLikelyHTML {
34+
// not HTML, write back directly
35+
_, err := p.output.Write(buf)
36+
return err
37+
}
38+
39+
// add our extra head HTML into output
40+
headBytes := []byte("<head>")
41+
posHead := bytes.Index(buf, headBytes)
42+
var part1, part2 []byte
43+
if posHead >= 0 {
44+
part1, part2 = buf[:posHead+len(headBytes)], buf[posHead+len(headBytes):]
45+
} else {
46+
part1, part2 = nil, buf
47+
}
48+
if len(part1) > 0 {
49+
if _, err := p.output.Write(part1); err != nil {
50+
return err
51+
}
52+
}
53+
if _, err := io.WriteString(p.output, string(p.extraHeadHTML)); err != nil {
54+
return err
55+
}
56+
_, err := p.output.Write(part2)
2957
return err
3058
}

modules/markup/internal/internal_test.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/stretchr/testify/assert"
1313
)
1414

15-
func TestRenderInternal(t *testing.T) {
15+
func TestRenderInternalAttrs(t *testing.T) {
1616
cases := []struct {
1717
input, protected, recovered string
1818
}{
@@ -30,7 +30,7 @@ func TestRenderInternal(t *testing.T) {
3030
for _, c := range cases {
3131
var r RenderInternal
3232
out := &bytes.Buffer{}
33-
in := r.init("sec", out)
33+
in := r.init("sec", out, "")
3434
protected := r.ProtectSafeAttrs(template.HTML(c.input))
3535
assert.EqualValues(t, c.protected, protected)
3636
_, _ = io.WriteString(in, string(protected))
@@ -41,7 +41,7 @@ func TestRenderInternal(t *testing.T) {
4141
var r1, r2 RenderInternal
4242
protected := r1.ProtectSafeAttrs(`<div class="test"></div>`)
4343
assert.EqualValues(t, `<div class="test"></div>`, protected, "non-initialized RenderInternal should not protect any attributes")
44-
_ = r1.init("sec", nil)
44+
_ = r1.init("sec", nil, "")
4545
protected = r1.ProtectSafeAttrs(`<div class="test"></div>`)
4646
assert.EqualValues(t, `<div data-attr-class="sec:test"></div>`, protected)
4747
assert.Equal(t, "data-attr-class", r1.SafeAttr("class"))
@@ -54,8 +54,37 @@ func TestRenderInternal(t *testing.T) {
5454
assert.Empty(t, recovered)
5555

5656
out2 := &bytes.Buffer{}
57-
in2 := r2.init("sec-other", out2)
57+
in2 := r2.init("sec-other", out2, "")
5858
_, _ = io.WriteString(in2, string(protected))
5959
_ = in2.Close()
6060
assert.Equal(t, `<div data-attr-class="sec:test"></div>`, out2.String(), "different secureID should not recover the value")
6161
}
62+
63+
func TestRenderInternalExtraHead(t *testing.T) {
64+
t.Run("HeadExists", func(t *testing.T) {
65+
out := &bytes.Buffer{}
66+
var r RenderInternal
67+
in := r.init("sec", out, `<MY-TAG>`)
68+
_, _ = io.WriteString(in, `<head>any</head>`)
69+
_ = in.Close()
70+
assert.Equal(t, `<head><MY-TAG>any</head>`, out.String())
71+
})
72+
73+
t.Run("HeadNotExists", func(t *testing.T) {
74+
out := &bytes.Buffer{}
75+
var r RenderInternal
76+
in := r.init("sec", out, `<MY-TAG>`)
77+
_, _ = io.WriteString(in, `<div></div>`)
78+
_ = in.Close()
79+
assert.Equal(t, `<MY-TAG><div></div>`, out.String())
80+
})
81+
82+
t.Run("NotHTML", func(t *testing.T) {
83+
out := &bytes.Buffer{}
84+
var r RenderInternal
85+
in := r.init("sec", out, `<MY-TAG>`)
86+
_, _ = io.WriteString(in, `<any>`)
87+
_ = in.Close()
88+
assert.Equal(t, `<any>`, out.String())
89+
})
90+
}

modules/markup/internal/renderinternal.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ type RenderInternal struct {
2929
secureIDPrefix string
3030
}
3131

32-
func (r *RenderInternal) Init(output io.Writer) io.WriteCloser {
32+
func (r *RenderInternal) Init(output io.Writer, extraHeadHTML template.HTML) io.WriteCloser {
3333
buf := make([]byte, 12)
3434
_, err := rand.Read(buf)
3535
if err != nil {
3636
panic("unable to generate secure id")
3737
}
38-
return r.init(base64.URLEncoding.EncodeToString(buf), output)
38+
return r.init(base64.URLEncoding.EncodeToString(buf), output, extraHeadHTML)
3939
}
4040

41-
func (r *RenderInternal) init(secID string, output io.Writer) io.WriteCloser {
41+
func (r *RenderInternal) init(secID string, output io.Writer, extraHeadHTML template.HTML) io.WriteCloser {
4242
r.secureID = secID
4343
r.secureIDPrefix = r.secureID + ":"
44-
return &finalProcessor{renderInternal: r, output: output}
44+
return &finalProcessor{renderInternal: r, output: output, extraHeadHTML: extraHeadHTML}
4545
}
4646

4747
func (r *RenderInternal) RecoverProtectedValue(v string) (string, bool) {

0 commit comments

Comments
 (0)