fix(utf8): закрыть lossy-границы конфигурации и API

This commit is contained in:
2026-09-07 17:25:27 +05:00
parent ab788725cf
commit af9f476658
38 changed files with 759 additions and 159 deletions
+40
View File
@@ -491,6 +491,46 @@ func TestLoginEndpointIssuesTokenForValidCredentials(t *testing.T) {
}
}
// Повреждённые байты и непарные surrogate escape не имеют права превратиться
// в U+FFFD до проверки пароля. Сам U+FFFD остаётся обычным допустимым символом.
func TestLoginEndpointRejectsLossyJSONBeforeAuthentication(t *testing.T) {
password := "abcde" + string(rune(0xFFFD))
id := newAuthTestDB(t, "hy2xsadmin", password)
for _, body := range [][]byte{
[]byte{'{', '"', 'u', 's', 'e', 'r', 'n', 'a', 'm', 'e', '"', ':', '"', 'h', 'y', '2', 'x', 's', 'a', 'd', 'm', 'i', 'n', '"', ',', '"', 'p', 'a', 's', 's', '"', ':', '"', 0xFF, '"', '}'},
[]byte(`{"username":"hy2xsadmin","pass":"abcde\uD800"}`),
[]byte(`{"username":"hy2xsadmin","pass":"abcde\uDC00"}`),
} {
status, result := postLoginRaw(t, body, false)
if status != http.StatusOK {
t.Fatalf("повреждённое тело дало HTTP %d", status)
}
if result.Code != constant.CodeInvalidError {
t.Fatalf("повреждённое тело не отклонено как invalid: %+v", result)
}
var found bool
for _, reason := range result.Errors {
found = found || reason.Code == constant.ErrCodeBodyInvalid
}
if !found {
t.Fatalf("нет причины %q: %+v", constant.ErrCodeBodyInvalid, result)
}
}
admin, err := dao.GetAdminUser("id = ?", id)
if err != nil {
t.Fatalf("не удалось проверить состояние администратора: %v", err)
}
if admin.LastLoginAt != nil && *admin.LastLoginAt != 0 {
t.Fatalf("отклонённое JSON-тело изменило last_login_at: %d", *admin.LastLoginAt)
}
_, valid := postLogin(t, map[string]any{"username": "hy2xsadmin", "pass": password})
if valid.Code != constant.CodeSuccess {
t.Fatalf("настоящий U+FFFD ошибочно отклонён: %+v", valid)
}
}
// Тот же запрос ЗА gin.Recovery: именно так он шёл в бою.
//
// В RC2 здесь был HTTP 500 — панику валидатора Recovery превращал в системную