mysqladm/server/
input_sanitization.rs

1use crate::core::{
2    common::UnixUser,
3    protocol::request_validation::{NameValidationError, OwnerValidationError},
4};
5
6const MAX_NAME_LENGTH: usize = 64;
7
8pub fn validate_name(name: &str) -> Result<(), NameValidationError> {
9    if name.is_empty() {
10        Err(NameValidationError::EmptyString)
11    } else if name.len() > MAX_NAME_LENGTH {
12        Err(NameValidationError::TooLong)
13    } else if !name
14        .chars()
15        .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
16    {
17        Err(NameValidationError::InvalidCharacters)
18    } else {
19        Ok(())
20    }
21}
22
23pub fn validate_ownership_by_unix_user(
24    name: &str,
25    user: &UnixUser,
26) -> Result<(), OwnerValidationError> {
27    let prefixes = std::iter::once(user.username.to_owned())
28        .chain(user.groups.iter().cloned())
29        .collect::<Vec<String>>();
30
31    validate_ownership_by_prefixes(name, &prefixes)
32}
33
34/// Core logic for validating the ownership of a database name.
35/// This function checks if the given name matches any of the given prefixes.
36/// These prefixes will in most cases be the user's unix username and any
37/// unix groups the user is a member of.
38pub fn validate_ownership_by_prefixes(
39    name: &str,
40    prefixes: &[String],
41) -> Result<(), OwnerValidationError> {
42    if name.is_empty() {
43        return Err(OwnerValidationError::StringEmpty);
44    }
45
46    if prefixes
47        .iter()
48        .filter(|p| name.starts_with(&(p.to_string() + "_")))
49        .collect::<Vec<_>>()
50        .is_empty()
51    {
52        return Err(OwnerValidationError::NoMatch);
53    };
54
55    Ok(())
56}
57
58#[inline]
59pub fn quote_literal(s: &str) -> String {
60    format!("'{}'", s.replace('\'', r"\'"))
61}
62
63#[inline]
64pub fn quote_identifier(s: &str) -> String {
65    format!("`{}`", s.replace('`', r"\`"))
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    #[test]
72    fn test_quote_literal() {
73        let payload = "' OR 1=1 --";
74        assert_eq!(quote_literal(payload), r#"'\' OR 1=1 --'"#);
75    }
76
77    #[test]
78    fn test_quote_identifier() {
79        let payload = "` OR 1=1 --";
80        assert_eq!(quote_identifier(payload), r#"`\` OR 1=1 --`"#);
81    }
82
83    #[test]
84    fn test_validate_name() {
85        assert_eq!(validate_name(""), Err(NameValidationError::EmptyString));
86        assert_eq!(validate_name("abcdefghijklmnopqrstuvwxyz"), Ok(()));
87        assert_eq!(validate_name("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), Ok(()));
88        assert_eq!(validate_name("0123456789_-"), Ok(()));
89
90        for c in "\n\t\r !@#$%^&*()+=[]{}|;:,.<>?/".chars() {
91            assert_eq!(
92                validate_name(&c.to_string()),
93                Err(NameValidationError::InvalidCharacters)
94            );
95        }
96
97        assert_eq!(validate_name(&"a".repeat(MAX_NAME_LENGTH)), Ok(()));
98
99        assert_eq!(
100            validate_name(&"a".repeat(MAX_NAME_LENGTH + 1)),
101            Err(NameValidationError::TooLong)
102        );
103    }
104
105    #[test]
106    fn test_validate_owner_by_prefixes() {
107        let prefixes = vec!["user".to_string(), "group".to_string()];
108
109        assert_eq!(
110            validate_ownership_by_prefixes("", &prefixes),
111            Err(OwnerValidationError::StringEmpty)
112        );
113
114        assert_eq!(
115            validate_ownership_by_prefixes("user_testdb", &prefixes),
116            Ok(())
117        );
118        assert_eq!(
119            validate_ownership_by_prefixes("group_testdb", &prefixes),
120            Ok(())
121        );
122        assert_eq!(
123            validate_ownership_by_prefixes("group_test_db", &prefixes),
124            Ok(())
125        );
126        assert_eq!(
127            validate_ownership_by_prefixes("group_test-db", &prefixes),
128            Ok(())
129        );
130
131        assert_eq!(
132            validate_ownership_by_prefixes("nonexistent_testdb", &prefixes),
133            Err(OwnerValidationError::NoMatch)
134        );
135    }
136}