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}