fix: 目录搜索转义 LIKE 通配符并拒绝编号 inline
This commit is contained in:
@@ -1376,3 +1376,12 @@ issue #3 未关闭,`feat/fix-3-downlink-deadlock` 未合入 `main`。下面是
|
||||
- 原因:注册安全码错误与错误 API 令牌按 IP 建条目,轮换地址会使 map 只增不减。
|
||||
- 备选方案:一并改 `internal/admin/memlock.go`(否决,本波只改 locks.go;admin 测试用内存锁若仍独立注入需后续对齐)。未改对话密码锁键(U-03)。
|
||||
- 影响:过期未锁定条目会被回收;极端并发失败时最早的非锁定计数可能被挤出。
|
||||
|
||||
### 复审修复 U-05
|
||||
|
||||
1. **目录 LIKE 转义;注册拒绝编号 inline**
|
||||
- 原条款:DEVELOPMENT 6.5 按编号前缀或名称包含匹配;issue #43。
|
||||
- 实际做法:目录查询对 `\`、`%`、`_` 转义并 `ESCAPE '\'`。注册拒绝编号 `inline`(与 mochi 内联客户端 ClientID 同名)。
|
||||
- 原因:未转义时搜 `e_ab` 会命中 `exab…`,搜 `%` 返回全部端。
|
||||
- 备选方案:在 `internal/protocol.ValidEndpointID` 加保留字,使开通/导入一并拒绝(否决,本波不改 protocol 与 `internal/admin/endpoints.go`)。后台开通与批量导入仍可能使用 `inline`,留给后续波次。
|
||||
- 影响:目录下划线按字面匹配;自助注册不能占用 `inline`。
|
||||
|
||||
@@ -171,6 +171,9 @@ func (h *RegisterHandler) register(ctx context.Context, req *protocol.RegisterRe
|
||||
}
|
||||
return RegisterResult{}, apiErr(http.StatusBadRequest, code, msg)
|
||||
}
|
||||
if strings.EqualFold(req.ID, "inline") {
|
||||
return RegisterResult{}, apiErr(http.StatusBadRequest, protocol.CodeBadRequest, "id reserved")
|
||||
}
|
||||
|
||||
id := req.ID
|
||||
loginPassword := req.LoginPassword
|
||||
|
||||
@@ -539,6 +539,18 @@ func TestRegister_LogOmitsSecrets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_ReservedInlineID(t *testing.T) {
|
||||
env := openTestEnv(t)
|
||||
env.setRegistration(t, true, "inline-code1")
|
||||
code, resp, _ := env.doRegister(t, `{"registration_code":"inline-code1","id":"inline","login_password":"password1"}`)
|
||||
if code != http.StatusBadRequest || resp.Error == nil || resp.Error.Code != protocol.CodeBadRequest {
|
||||
t.Fatalf("status=%d resp=%+v", code, resp)
|
||||
}
|
||||
if _, _, ok := env.getEndpoint(t, "inline"); ok {
|
||||
t.Fatal("inline must not be created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_NSTPasswordRejected(t *testing.T) {
|
||||
env := openTestEnv(t)
|
||||
env.setRegistration(t, true, "nst-code-01")
|
||||
|
||||
@@ -122,13 +122,15 @@ WHERE id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?`, cursor, limit+1)
|
||||
} else {
|
||||
like := "%" + strings.ToLower(query) + "%"
|
||||
prefix := strings.ToLower(query) + "%"
|
||||
q := strings.ToLower(query)
|
||||
esc := escapeLikePattern(q)
|
||||
like := "%" + esc + "%"
|
||||
prefix := esc + "%"
|
||||
rows, err = a.db.Read.QueryContext(ctx, `
|
||||
SELECT id, name, online_since, offline_since, talk_hash
|
||||
FROM endpoints
|
||||
WHERE id > ?
|
||||
AND (lower(id) LIKE ? OR lower(name) LIKE ?)
|
||||
AND (lower(id) LIKE ? ESCAPE '\' OR lower(name) LIKE ? ESCAPE '\')
|
||||
ORDER BY id ASC
|
||||
LIMIT ?`, cursor, prefix, like, limit+1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package presence
|
||||
|
||||
import "strings"
|
||||
|
||||
func escapeLikePattern(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s) + 4)
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\\', '%', '_':
|
||||
b.WriteByte('\\')
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
@@ -131,6 +131,35 @@ func TestF03PresenceAndDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectoryLikeEscapesUnderscore(t *testing.T) {
|
||||
t.Parallel()
|
||||
app, db, _ := openPresence(t)
|
||||
ctx := context.Background()
|
||||
insertEP(t, db, "e_ab1", "underscore")
|
||||
insertEP(t, db, "exab2", "wildcard")
|
||||
insertEP(t, db, "pct", "has%percent")
|
||||
|
||||
q, _, err := app.Directory(ctx, &protocol.DirectoryList{
|
||||
V: protocol.Version, Type: protocol.TypeDirectoryList, RID: "u1", Query: "e_ab", Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(q) != 1 || q[0].ID != "e_ab1" {
|
||||
t.Fatalf("want only e_ab1, got %+v", q)
|
||||
}
|
||||
|
||||
q, _, err = app.Directory(ctx, &protocol.DirectoryList{
|
||||
V: protocol.Version, Type: protocol.TypeDirectoryList, RID: "u2", Query: "%", Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(q) != 1 || q[0].ID != "pct" {
|
||||
t.Fatalf("literal %% should not match all, got %+v", q)
|
||||
}
|
||||
}
|
||||
|
||||
func TestF04PresenceWatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
app, db, down := openPresence(t)
|
||||
|
||||
Reference in New Issue
Block a user