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