Files
2026-07-02 10:24:19 +08:00

110 lines
3.6 KiB
Go

// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package model
import "testing"
// TestIsValidSearchBoxPath 覆盖搜索入参的笔记本 ID 与文档路径校验,阻止 SQL 元字符进入语句拼接。
// 回归用例参考 /api/search/fullTextSearchBlock 的 SQL 注入报告(paths[] 投毒)。
func TestIsValidSearchBoxPath(t *testing.T) {
validBox := "20210808180117-6v0mkxr"
validCases := []struct {
name string
box string
path string
}{
{"仅笔记本范围", validBox, ""},
{"仅斜杠", validBox, "/"},
{"具体文档", validBox, "/20210808180117-6v0mkxr.sy"},
{"子树目录范围", validBox, "/20210808180117-6v0mkxr"},
{"子文档完整路径", validBox, "/20210808180117-6v0mkxr/20210808180530-a1b2c3d.sy"},
}
for _, tc := range validCases {
t.Run("valid/"+tc.name, func(t *testing.T) {
if !IsValidSearchBoxPath(tc.box, tc.path) {
t.Fatalf("expected valid: box=%q path=%q", tc.box, tc.path)
}
})
}
invalidCases := []struct {
name string
box string
path string
}{
// 报告中的 UNION 投毒 payload
{
"SQL注入UNION投影",
validBox,
"/x%') UNION SELECT id,parent_id FROM blocks WHERE path='/hidden.sy' -- ",
},
{"单引号断字符串", validBox, "/doc'secret.sy"},
{"百分号前导", validBox, "/%abc"},
{"注释标记", validBox, "/doc -- "},
{"非法box短数字", "123", ""},
{"非法box大写", "20210808180117-6V0MKXR", ""},
{"非法box空", "", "/20210808180117-6v0mkxr.sy"},
{"path缺少前导斜杠", validBox, "20210808180117-6v0mkxr.sy"},
{"path段非法", validBox, "/notanid.sy"},
{"path中段非法", validBox, "/20210808180117-6v0mkxr/notanid.sy"},
}
for _, tc := range invalidCases {
t.Run("invalid/"+tc.name, func(t *testing.T) {
if IsValidSearchBoxPath(tc.box, tc.path) {
t.Fatalf("expected invalid: box=%q path=%q", tc.box, tc.path)
}
})
}
}
// TestBuildBoxesPathFiltersArgCount 验证参数化过滤器产出的 "?" 数量与 args 长度一致。
func TestBuildBoxesPathFiltersArgCount(t *testing.T) {
boxes := []string{"20210808180117-6v0mkxr", "20210808180117-a1b2c3d"}
clause, args := buildBoxesFilter(boxes)
if countPlaceholder(clause) != len(args) {
t.Fatalf("box filter placeholder/arg mismatch: %q vs %d args", clause, len(args))
}
if len(args) != 2 {
t.Fatalf("expected 2 box args, got %d", len(args))
}
paths := []string{"/20210808180117-6v0mkxr", "/20210808180117-a1b2c3d/20210808180530-e5f6g7h.sy"}
clause, args = buildPathsFilter(paths)
if countPlaceholder(clause) != len(args) {
t.Fatalf("path filter placeholder/arg mismatch: %q vs %d args", clause, len(args))
}
if len(args) != 2 {
t.Fatalf("expected 2 path args, got %d", len(args))
}
for i, a := range args {
s, ok := a.(string)
if !ok || s != paths[i]+"%" {
t.Fatalf("path arg %d should be %q%%, got %v", i, paths[i], a)
}
}
}
func countPlaceholder(s string) (n int) {
for i := 0; i < len(s); i++ {
if s[i] == '?' {
n++
}
}
return
}