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}