From 8c00a95d74de9e65a3d5495ff8b8ce60206f14dd Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:14:59 +0800 Subject: [PATCH 1/5] :bug: Attribute values are not escaped https://github.com/siyuan-note/siyuan/issues/16686 (#16712) --- kernel/filesys/tree.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index 47ec280ef..6a66c8e45 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -28,6 +28,8 @@ import ( "sync" "github.com/88250/lute" + "github.com/88250/lute/ast" + "github.com/88250/lute/html" "github.com/88250/lute/parse" "github.com/88250/lute/render" jsoniter "github.com/json-iterator/go" @@ -292,6 +294,10 @@ func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (re needFix = true } + if escapeAttributeValues(ret) { + needFix = true + } + if pathID := util.GetTreeID(p); pathID != ret.Root.ID { needFix = true logging.LogInfof("reset tree id from [%s] to [%s]", ret.Root.ID, pathID) @@ -324,3 +330,86 @@ func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (re } return } + +// escapeAttributeValues 转义属性值 +func escapeAttributeValues(tree *parse.Tree) (hasEscaped bool) { + if util.ReadOnly || nil == tree || nil == tree.Root { + return false + } + + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() || "" == n.ID || 0 == len(n.KramdownIAL) { + return ast.WalkContinue + } + + if escaped := escapeNodeAttributeValues(n); escaped { + hasEscaped = true + } + + return ast.WalkContinue + }) + + return hasEscaped +} + +// escapeNodeAttributeValues 转义节点的属性值 +func escapeNodeAttributeValues(node *ast.Node) (escaped bool) { + if nil == node || 0 == len(node.KramdownIAL) { + return false + } + + attrs := parse.IAL2Map(node.KramdownIAL) + needsEscape := false + escapedAttrs := make(map[string]string) + + for key, value := range attrs { + if needsEscapeForValue(value) { + escapedAttrs[key] = html.EscapeAttrVal(value) + needsEscape = true + } + } + + if !needsEscape { + return false + } + + oldAttrs := parse.IAL2Map(node.KramdownIAL) + newAttrs := make(map[string]string) + for k, v := range oldAttrs { + newAttrs[k] = v + } + for name, value := range escapedAttrs { + lowerName := strings.ToLower(name) + delete(newAttrs, name) + newAttrs[lowerName] = value + } + node.KramdownIAL = parse.Map2IAL(newAttrs) + + return true +} + +// needsEscapeForValue 检查值是否需要转义(包含需要转义的特殊字符但尚未被转义) +func needsEscapeForValue(value string) bool { + hasSpecialChars := false + for _, char := range value { + switch char { + case '<', '>', '&', '"', '{', '}': + hasSpecialChars = true + } + if hasSpecialChars { + break + } + } + if !hasSpecialChars { + return false + } + + entities := []string{""", "{", "}", "&", "<", ">"} + for _, entity := range entities { + if strings.Contains(value, entity) { + return false + } + } + + return true +} From 0ebefd62932783f54110d0b48e515cd997c9e6e2 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Dec 2025 15:32:10 +0800 Subject: [PATCH 2/5] :bug: Attribute values are not escaped https://github.com/siyuan-note/siyuan/pull/16712 Signed-off-by: Daniel <845765@qq.com> --- kernel/filesys/tree.go | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index 6a66c8e45..ecb6a22fb 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -345,10 +345,8 @@ func escapeAttributeValues(tree *parse.Tree) (hasEscaped bool) { if escaped := escapeNodeAttributeValues(n); escaped { hasEscaped = true } - return ast.WalkContinue }) - return hasEscaped } @@ -358,34 +356,13 @@ func escapeNodeAttributeValues(node *ast.Node) (escaped bool) { return false } - attrs := parse.IAL2Map(node.KramdownIAL) - needsEscape := false - escapedAttrs := make(map[string]string) - - for key, value := range attrs { - if needsEscapeForValue(value) { - escapedAttrs[key] = html.EscapeAttrVal(value) - needsEscape = true + for _, kv := range node.KramdownIAL { + if value := kv[1]; needsEscapeForValue(value) { + kv[1] = html.EscapeAttrVal(value) + escaped = true } } - - if !needsEscape { - return false - } - - oldAttrs := parse.IAL2Map(node.KramdownIAL) - newAttrs := make(map[string]string) - for k, v := range oldAttrs { - newAttrs[k] = v - } - for name, value := range escapedAttrs { - lowerName := strings.ToLower(name) - delete(newAttrs, name) - newAttrs[lowerName] = value - } - node.KramdownIAL = parse.Map2IAL(newAttrs) - - return true + return } // needsEscapeForValue 检查值是否需要转义(包含需要转义的特殊字符但尚未被转义) @@ -410,6 +387,5 @@ func needsEscapeForValue(value string) bool { return false } } - return true } From be67b887c379129db2cdb5b6b6ddb9a61fc61ebd Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Dec 2025 15:36:00 +0800 Subject: [PATCH 3/5] :bug: Attribute values are not escaped https://github.com/siyuan-note/siyuan/pull/16712 https://github.com/siyuan-note/siyuan/issues/16686 Signed-off-by: Daniel <845765@qq.com> --- kernel/filesys/tree.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index ecb6a22fb..6c66cc984 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -294,7 +294,9 @@ func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (re needFix = true } - if escapeAttributeValues(ret) { + if escapeAttributeValues(ret) { // TODO 划于 2026 年 6 月 30 日后删除 + // v3.5.1 https://github.com/siyuan-note/siyuan/pull/16657 引入的问题,属性值未转义 + // v3.5.2 https://github.com/siyuan-note/siyuan/issues/16686 进行了修复,并加了订正逻辑 https://github.com/siyuan-note/siyuan/pull/16712 needFix = true } From a3548825e3cf36d4331abf91c90539694f35d94b Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Dec 2025 15:45:50 +0800 Subject: [PATCH 4/5] :bug: Attribute values are not escaped https://github.com/siyuan-note/siyuan/pull/16712 https://github.com/siyuan-note/siyuan/issues/16686 Signed-off-by: Daniel <845765@qq.com> --- kernel/filesys/tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index 6c66cc984..6e94ddb3f 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -294,7 +294,7 @@ func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (re needFix = true } - if escapeAttributeValues(ret) { // TODO 划于 2026 年 6 月 30 日后删除 + if escapeAttributeValues(ret) { // TODO 计划于 2026 年 6 月 30 日后删除 // v3.5.1 https://github.com/siyuan-note/siyuan/pull/16657 引入的问题,属性值未转义 // v3.5.2 https://github.com/siyuan-note/siyuan/issues/16686 进行了修复,并加了订正逻辑 https://github.com/siyuan-note/siyuan/pull/16712 needFix = true From 7679adf6d578a37c275b7cba9985c5a7145845ef Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Dec 2025 16:39:13 +0800 Subject: [PATCH 5/5] :art: Improve auto email link parsing https://github.com/siyuan-note/siyuan/issues/16629 Signed-off-by: Daniel <845765@qq.com> --- kernel/util/path.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/util/path.go b/kernel/util/path.go index 87a35604e..e2f39a6ef 100644 --- a/kernel/util/path.go +++ b/kernel/util/path.go @@ -165,6 +165,14 @@ func IsRelativePath(dest string) bool { if '/' == dest[0] { return false } + + // 检查特定协议前缀 + lowerDest := strings.ToLower(dest) + if strings.HasPrefix(lowerDest, "mailto:") || + strings.HasPrefix(lowerDest, "tel:") || + strings.HasPrefix(lowerDest, "sms:") { + return false + } return !strings.Contains(dest, ":/") && !strings.Contains(dest, ":\\") }