diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 8431853e1d6..2dcbf66c8d7 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -198,7 +198,7 @@ ;; For the built-in SSH server, choose the keypair to offer as the host key ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub ;; relative paths are made absolute relative to the APP_DATA_PATH -;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa +;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gitea.ed25519, ssh/gitea.ecdsa, ssh/gogs.rsa ;; ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false ;SSH_AUTHORIZED_KEYS_BACKUP = false @@ -237,7 +237,7 @@ ;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s ;; ;; Indicate whether to check minimum key size with corresponding type -;MINIMUM_KEY_SIZE_CHECK = false +;MINIMUM_KEY_SIZE_CHECK = true ;; ;; TLS Settings: Either ACME or manual ;; (Other common TLS configuration are found before) @@ -1669,13 +1669,13 @@ LEVEL = Info ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy -;; default to persistable-channel -;TYPE = persistable-channel +;; General queue type, currently support: level, channel, redis, dummy +;; default to level +;TYPE = level ;; -;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. +;; data-dir for storing level queues, individual queues will default to `queues/common` meaning the queue is shared. ;; Relative paths will be made absolute against "APP_DATA_PATH" -;DATADIR = queues/ +;DATADIR = queues/common ;; ;; Default queue length before a channel queue will block ;LENGTH = 100000 @@ -1683,7 +1683,7 @@ LEVEL = Info ;; Batch size to send for batched queues ;BATCH_LENGTH = 20 ;; -;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb +;; When `TYPE` is `level`, this provides a directory for the underlying leveldb ;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`. ;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR. ;CONN_STR = diff --git a/models/user/search.go b/models/user/search.go index 7f835dc3ff7..8520f8c9fd4 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -40,8 +40,8 @@ type SearchUserOptions struct { Keyword string Types []UserType UID int64 - LoginName string // this option should be used only for admin user - SourceID int64 // this option should be used only for admin user + LoginName string // this option should be used only for admin user + SourceID optional.Option[int64] // this option should be used only for admin user, Some(0) means local users OrderBy db.SearchOrderBy Visible []structs.VisibleType Actor *User // The user doing the search @@ -106,8 +106,8 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) db.Session cond = cond.And(builder.Eq{"id": opts.UID}) } - if opts.SourceID > 0 { - cond = cond.And(builder.Eq{"login_source": opts.SourceID}) + if opts.SourceID.Has() { + cond = cond.And(builder.Eq{"login_source": opts.SourceID.Value()}) } if opts.LoginName != "" { cond = cond.And(builder.Eq{"login_name": opts.LoginName}) diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 360e77782fb..df31c3b16cf 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -8,11 +8,13 @@ import ( "fmt" "html" "io" + "strings" "unicode" "unicode/utf8" "gitea.dev/modules/setting" "gitea.dev/modules/translation" + "gitea.dev/modules/util" ) type htmlChunkReader struct { @@ -30,6 +32,10 @@ type escapeStreamer struct { ambiguousTables []*AmbiguousTable allowed map[rune]bool + tagPartial []byte // partial tag content, used to detect if we are in some tags + + inTagMath bool // MathML operators like U+2212 are intended and wrapping them breaks the math layout + out io.Writer } @@ -62,6 +68,7 @@ func escapeStream(locale translation.Locale, in io.Reader, out io.Writer, opts . for i, part := range parts { if partInTag[i] { lastIsTag = true + es.trackHtmlTag(part) if _, err := out.Write(part); err != nil { return nil, err } @@ -75,7 +82,11 @@ func escapeStream(locale translation.Locale, in io.Reader, out io.Writer, opts . return nil, err } } - if err = es.detectAndWriteRunes(part); err != nil { + if es.inTagMath { + if _, err := out.Write(part); err != nil { + return nil, err + } + } else if err = es.detectAndWriteRunes(part); err != nil { return nil, err } } @@ -83,6 +94,34 @@ func escapeStream(locale translation.Locale, in io.Reader, out io.Writer, opts . } } +// trackHtmlTag receives tag parts, a tag might be split into multiple parts +func (e *escapeStreamer) trackHtmlTag(part []byte) { + const maxHeadLen = 100 // only read the first N bytes of the tag for detection purpose + if part[0] == '<' { + // start a new tag + e.tagPartial = e.tagPartial[:0] + } + if len(e.tagPartial) >= maxHeadLen { + return + } + e.tagPartial = append(e.tagPartial, part[:min(len(part), maxHeadLen-len(e.tagPartial))]...) + + isTag := func(prefix string) bool { + if len(e.tagPartial) < len(prefix)+1 { + return false + } + if !util.AsciiEqualFold(e.tagPartial[:len(prefix)], []byte(prefix)) { + return false + } + return strings.IndexByte(" \t\n\r\f>", e.tagPartial[len(prefix)]) != -1 + } + if isTag("๐พ`, status: EscapeStatus{Escaped: true, HasAmbiguous: true}, }, + { + name: "ambiguous in math", + text: "โˆ’b โˆ’", + result: `โˆ’b โˆ’`, + status: EscapeStatus{Escaped: true, HasAmbiguous: true}, + }, } func TestEscapeControlReader(t *testing.T) { @@ -156,6 +162,24 @@ func TestEscapeControlReader(t *testing.T) { } } +func TestTrackHtmlTag(t *testing.T) { + e := &escapeStreamer{} + for _, tt := range []struct { + parts []string + inMath bool + }{ + {[]string{"`}, true}, + {[]string{""}, true}, + {[]string{""}, false}, + {[]string{""}, false}, + } { + for _, part := range tt.parts { + e.trackHtmlTag([]byte(part)) + } + assert.Equal(t, tt.inMath, e.inTagMath, "%v", tt.parts) + } +} + func TestSettingAmbiguousUnicodeDetection(t *testing.T) { defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)() _, out := EscapeControlHTML("aย test", &translation.MockLocale{}) diff --git a/modules/markup/html.go b/modules/markup/html.go index 4687244668b..f64114226e1 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -349,8 +349,8 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod // TextNode emoji will be converted to ``, then the next iteration will visit the "span" // if we don't stop it, it will go into the TextNode again and create an infinite recursion return node.NextSibling - } else if node.Data == "code" || node.Data == "pre" { - return node.NextSibling // ignore code and pre nodes + } else if node.Data == "code" || node.Data == "pre" || node.Data == "math" { + return node.NextSibling // ignore code, pre and math nodes } else if node.Data == "img" { return visitNodeImg(ctx, node) } else if node.Data == "video" { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 8881edb9486..f4b44a5cbe1 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -543,6 +543,9 @@ func TestPostProcess(t *testing.T) { `Some text with ๐Ÿ˜„ in the middle`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`) + test( + ":gitea: go-gitea/gitea#12345", + ":gitea: go-gitea/gitea#12345") // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible test("