Skip to main content

grant/config/
sql_utils.rs

1//! SQL utility functions for safe query construction.
2//!
3//! This module provides functions to safely escape and quote SQL identifiers
4//! and string literals to prevent SQL injection vulnerabilities.
5
6/// Escape and quote a PostgreSQL identifier to prevent SQL injection.
7///
8/// PostgreSQL identifiers (table names, column names, role names, etc.) must be
9/// quoted with double quotes and any internal double quotes must be escaped by
10/// doubling them.
11///
12/// # Examples
13///
14/// ```
15/// use grant::config::sql_utils::escape_identifier;
16///
17/// assert_eq!(escape_identifier("users"), "\"users\"");
18/// assert_eq!(escape_identifier("my\"table"), "\"my\"\"table\"");
19/// assert_eq!(escape_identifier("role'name"), "\"role'name\"");
20/// ```
21///
22/// # Security
23///
24/// This function prevents SQL injection by ensuring that user-provided
25/// identifiers cannot break out of their quoted context.
26pub fn escape_identifier(ident: &str) -> String {
27    // PostgreSQL identifiers are quoted with double quotes
28    // Escape double quotes by doubling them
29    format!("\"{}\"", ident.replace("\"", "\"\""))
30}
31
32/// Escape a string literal for use in SQL queries.
33///
34/// PostgreSQL string literals are quoted with single quotes and any internal
35/// single quotes must be escaped by doubling them.
36///
37/// # Examples
38///
39/// ```
40/// use grant::config::sql_utils::escape_sql_string;
41///
42/// assert_eq!(escape_sql_string("password"), "password");
43/// assert_eq!(escape_sql_string("pass'word"), "pass''word");
44/// assert_eq!(escape_sql_string("it's"), "it''s");
45/// ```
46///
47/// # Security
48///
49/// This function prevents SQL injection in string literals by escaping
50/// single quotes that could terminate the string.
51pub fn escape_sql_string(s: &str) -> String {
52    s.replace("'", "''")
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_escape_identifier_simple() {
61        assert_eq!(escape_identifier("users"), "\"users\"");
62        assert_eq!(escape_identifier("my_table"), "\"my_table\"");
63    }
64
65    #[test]
66    fn test_escape_identifier_with_quotes() {
67        assert_eq!(escape_identifier("my\"table"), "\"my\"\"table\"");
68        assert_eq!(
69            escape_identifier("\"already\"quoted\""),
70            "\"\"\"already\"\"quoted\"\"\""
71        );
72    }
73
74    #[test]
75    fn test_escape_identifier_with_single_quotes() {
76        // Single quotes don't need escaping in identifiers
77        assert_eq!(escape_identifier("role'name"), "\"role'name\"");
78    }
79
80    #[test]
81    fn test_escape_sql_string_simple() {
82        assert_eq!(escape_sql_string("password"), "password");
83        assert_eq!(escape_sql_string("simple"), "simple");
84    }
85
86    #[test]
87    fn test_escape_sql_string_with_quotes() {
88        assert_eq!(escape_sql_string("pass'word"), "pass''word");
89        assert_eq!(escape_sql_string("it's"), "it''s");
90        assert_eq!(escape_sql_string("'quoted'"), "''quoted''");
91    }
92
93    #[test]
94    fn test_escape_sql_string_with_double_quotes() {
95        // Double quotes don't need escaping in string literals
96        assert_eq!(escape_sql_string("my\"value"), "my\"value");
97    }
98}