sz_orm_core/
sql_safety.rs1use crate::DbError;
9
10pub fn validate_identifier(name: &str, kind: &str) -> Result<(), DbError> {
15 if name.is_empty() || name.len() > 63 {
16 return Err(DbError::InvalidInput(format!(
17 "invalid {}: empty or too long (max 63 chars): {:?}",
18 kind, name
19 )));
20 }
21 let mut chars = name.chars();
22 let first = chars.next().ok_or_else(|| {
23 DbError::InvalidInput(format!(
24 "invalid {}: empty string after length check: {:?}",
25 kind, name
26 ))
27 })?;
28 if !first.is_ascii_alphabetic() && first != '_' {
29 return Err(DbError::InvalidInput(format!(
30 "invalid {}: must start with ASCII letter or underscore, got {:?}",
31 kind, name
32 )));
33 }
34 if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
35 return Err(DbError::InvalidInput(format!(
36 "invalid {}: only ASCII alphanumeric and underscore allowed, got {:?}",
37 kind, name
38 )));
39 }
40 Ok(())
41}
42
43pub fn validate_fk_action(action: &str) -> Result<(), DbError> {
48 const ALLOWED: &[&str] = &[
49 "CASCADE",
50 "SET NULL",
51 "SET DEFAULT",
52 "RESTRICT",
53 "NO ACTION",
54 ];
55 let upper = action.trim().to_uppercase();
56 if !ALLOWED.contains(&upper.as_str()) {
57 return Err(DbError::InvalidInput(format!(
58 "invalid foreign key action: {:?}, allowed: {:?}",
59 action, ALLOWED
60 )));
61 }
62 Ok(())
63}
64
65pub fn validate_id_value(id: &str) -> Result<(), DbError> {
74 if id.is_empty() || id.len() > 128 {
75 return Err(DbError::InvalidInput(format!(
76 "invalid id value: empty or too long (max 128 chars): {:?}",
77 id
78 )));
79 }
80 if id.contains("--") {
82 return Err(DbError::InvalidInput(format!(
83 "invalid id value: SQL comment sequence '--' not allowed, got {:?}",
84 id
85 )));
86 }
87 if !id
88 .chars()
89 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
90 {
91 return Err(DbError::InvalidInput(format!(
92 "invalid id value: only ASCII alphanumeric, underscore and hyphen allowed, got {:?}",
93 id
94 )));
95 }
96 Ok(())
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_validate_identifier_valid() {
105 assert!(validate_identifier("users", "table").is_ok());
106 assert!(validate_identifier("_idx", "index").is_ok());
107 assert!(validate_identifier("geom_2026", "column").is_ok());
108 assert!(validate_identifier("a", "column").is_ok());
109 assert!(validate_identifier(&"a".repeat(63), "table").is_ok());
110 }
111
112 #[test]
113 fn test_validate_identifier_injection_attempts() {
114 assert!(validate_identifier("users; DROP TABLE users", "table").is_err());
116 assert!(validate_identifier("col'--", "column").is_err());
117 assert!(validate_identifier("col\"x", "column").is_err());
118 assert!(validate_identifier("col`x", "column").is_err());
119 assert!(validate_identifier("col--", "column").is_err());
120 assert!(validate_identifier("col/*x*/", "column").is_err());
121 assert!(validate_identifier("col OR 1=1", "column").is_err());
122 assert!(validate_identifier("1col", "column").is_err());
124 assert!(validate_identifier("", "table").is_err());
126 let long_name = "a".repeat(64);
128 assert!(validate_identifier(&long_name, "table").is_err());
129 assert!(validate_identifier("col name", "column").is_err());
131 assert!(validate_identifier("col$name", "column").is_err());
133 assert!(validate_identifier("col%name", "column").is_err());
134 assert!(validate_identifier("col@name", "column").is_err());
135 }
136
137 #[test]
138 fn test_validate_fk_action_valid() {
139 assert!(validate_fk_action("CASCADE").is_ok());
140 assert!(validate_fk_action("cascade").is_ok()); assert!(validate_fk_action("Cascade").is_ok());
142 assert!(validate_fk_action("SET NULL").is_ok());
143 assert!(validate_fk_action("set null").is_ok());
144 assert!(validate_fk_action("SET DEFAULT").is_ok());
145 assert!(validate_fk_action("RESTRICT").is_ok());
146 assert!(validate_fk_action("NO ACTION").is_ok());
147 assert!(validate_fk_action(" NO ACTION ").is_ok()); }
149
150 #[test]
151 fn test_validate_fk_action_injection_attempts() {
152 assert!(validate_fk_action("CASCADE; DROP TABLE users").is_err());
153 assert!(validate_fk_action("CASCADE--").is_err());
154 assert!(validate_fk_action("CASCADE OR 1=1").is_err());
155 assert!(validate_fk_action("EVIL").is_err());
156 assert!(validate_fk_action("' OR '1'='1").is_err());
157 assert!(validate_fk_action("").is_err());
158 }
159
160 #[test]
161 fn test_validate_id_value_valid() {
162 assert!(validate_id_value("1").is_ok());
163 assert!(validate_id_value("100").is_ok());
164 assert!(validate_id_value("abc").is_ok());
165 assert!(validate_id_value("user_123").is_ok());
166 assert!(validate_id_value("uuid-abc-123").is_ok());
167 assert!(validate_id_value(&"a".repeat(128)).is_ok());
168 }
169
170 #[test]
171 fn test_validate_id_value_injection_attempts() {
172 assert!(validate_id_value("1; DROP TABLE users").is_err());
174 assert!(validate_id_value("1) OR 1=1").is_err());
175 assert!(validate_id_value("' OR '1'='1").is_err());
176 assert!(validate_id_value("1--").is_err());
177 assert!(validate_id_value("1/*comment*/").is_err());
178 assert!(validate_id_value("1;").is_err());
179 assert!(validate_id_value("1'").is_err());
180 assert!(validate_id_value("1\"").is_err());
181 assert!(validate_id_value("").is_err());
183 let long_id = "a".repeat(129);
185 assert!(validate_id_value(&long_id).is_err());
186 assert!(validate_id_value("1 2").is_err());
188 assert!(validate_id_value("users.id").is_err());
190 }
191}