fix(mysql): prevent panic on Chinese characters in WHERE clause

Fix UTF-8 boundary issues in mysql_top_level_limit, parse_usize_token,
and mysql_keyword_at functions that caused crashes when SQL contains
Chinese characters in string literals.

The issue was introduced in commit 021e1682 which added byte-level
indexing without proper UTF-8 boundary checks. When parsing SQL with
Chinese text (e.g., \"WHERE name LIKE '%批量保存%'\"), the byte index
could point to the middle of a multi-byte UTF-8 character, causing
Rust to panic when slicing the string.

Changes:
- parse_usize_token: Use std::str::from_utf8 to validate slice before parsing
- mysql_keyword_at: Add is_char_boundary checks for safe string slicing
- mysql_top_level_limit: Skip non-UTF-8-boundary bytes after incrementing index

This ensures all byte-level indexing operations stay on valid UTF-8 boundaries,
preventing panics while maintaining the original functionality of detecting
LIMIT clauses for bounded result set collection.

Fixes crash reported by users when executing queries with Chinese conditions.
This commit is contained in:
bigliangzao
2026-06-17 19:00:05 +08:00
committed by GitHub
parent 9bc19cf3b0
commit 7643148708
+14 -3
View File
@@ -1468,7 +1468,11 @@ fn mysql_top_level_limit(sql: &str) -> Option<usize> {
if depth == 0 && mysql_keyword_at(sql, i, "LIMIT") {
return parse_mysql_limit_value(sql, i + "LIMIT".len());
}
// Move to next byte, but ensure we stay on a UTF-8 boundary
i += 1;
while i < bytes.len() && !sql.is_char_boundary(i) {
i += 1;
}
}
None
@@ -1497,13 +1501,20 @@ fn parse_usize_token(sql: &str, i: &mut usize) -> Option<usize> {
if *i == start {
return None;
}
sql[start..*i].parse().ok()
// Ensure the slice is valid UTF-8 before parsing
std::str::from_utf8(&bytes[start..*i]).ok()?.parse().ok()
}
fn mysql_keyword_at(sql: &str, i: usize, keyword: &str) -> bool {
let end = i + keyword.len();
end <= sql.len()
&& sql[i..end].eq_ignore_ascii_case(keyword)
if end > sql.len() {
return false;
}
// Ensure indices are on UTF-8 boundaries before slicing
if !sql.is_char_boundary(i) || !sql.is_char_boundary(end) {
return false;
}
sql[i..end].eq_ignore_ascii_case(keyword)
&& (i == 0 || !is_mysql_identifier_byte(sql.as_bytes()[i - 1]))
&& (end == sql.len() || !is_mysql_identifier_byte(sql.as_bytes()[end]))
}