postgres_protocol/escape/mod.rs
1//! Provides functions for escaping literals and identifiers for use
2//! in SQL queries.
3//!
4//! Prefer parameterized queries where possible. Do not escape
5//! parameters in a parameterized query.
6
7#[cfg(test)]
8mod test;
9
10/// Escape a literal and surround result with single quotes. Not
11/// recommended in most cases.
12///
13/// If input contains backslashes, result will be of the form `
14/// E'...'` so it is safe to use regardless of the setting of
15/// standard_conforming_strings.
16pub fn escape_literal(input: &str) -> String {
17 escape_internal(input, false)
18}
19
20/// Escape an identifier and surround result with double quotes.
21pub fn escape_identifier(input: &str) -> String {
22 escape_internal(input, true)
23}
24
25// Translation of PostgreSQL libpq's PQescapeInternal(). Does not
26// require a connection because input string is known to be valid
27// UTF-8.
28//
29// Escape arbitrary strings. If as_ident is true, we escape the
30// result as an identifier; if false, as a literal. The result is
31// returned in a newly allocated buffer. If we fail due to an
32// encoding violation or out of memory condition, we return NULL,
33// storing an error message into conn.
34fn escape_internal(input: &str, as_ident: bool) -> String {
35 let mut num_backslashes = 0;
36 let mut num_quotes = 0;
37 let quote_char = if as_ident { '"' } else { '\'' };
38
39 // Scan the string for characters that must be escaped.
40 for ch in input.chars() {
41 if ch == quote_char {
42 num_quotes += 1;
43 } else if ch == '\\' {
44 num_backslashes += 1;
45 }
46 }
47
48 // Allocate output String.
49 let mut result_size = input.len() + num_quotes + 3; // two quotes, plus a NUL
50 if !as_ident && num_backslashes > 0 {
51 result_size += num_backslashes + 2;
52 }
53
54 let mut output = String::with_capacity(result_size);
55
56 // If we are escaping a literal that contains backslashes, we use
57 // the escape string syntax so that the result is correct under
58 // either value of standard_conforming_strings. We also emit a
59 // leading space in this case, to guard against the possibility
60 // that the result might be interpolated immediately following an
61 // identifier.
62 if !as_ident && num_backslashes > 0 {
63 output.push(' ');
64 output.push('E');
65 }
66
67 // Opening quote.
68 output.push(quote_char);
69
70 // Use fast path if possible.
71 //
72 // We've already verified that the input string is well-formed in
73 // the current encoding. If it contains no quotes and, in the
74 // case of literal-escaping, no backslashes, then we can just copy
75 // it directly to the output buffer, adding the necessary quotes.
76 //
77 // If not, we must rescan the input and process each character
78 // individually.
79 if num_quotes == 0 && (num_backslashes == 0 || as_ident) {
80 output.push_str(input);
81 } else {
82 for ch in input.chars() {
83 if ch == quote_char || (!as_ident && ch == '\\') {
84 output.push(ch);
85 }
86 output.push(ch);
87 }
88 }
89
90 output.push(quote_char);
91
92 output
93}