Line data Source code
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)]
8 : mod 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.
16 4 : pub fn escape_literal(input: &str) -> String {
17 4 : escape_internal(input, false)
18 4 : }
19 :
20 : /// Escape an identifier and surround result with double quotes.
21 4 : pub fn escape_identifier(input: &str) -> String {
22 4 : escape_internal(input, true)
23 4 : }
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.
34 8 : fn escape_internal(input: &str, as_ident: bool) -> String {
35 8 : let mut num_backslashes = 0;
36 8 : let mut num_quotes = 0;
37 8 : let quote_char = if as_ident { '"' } else { '\'' };
38 :
39 : // Scan the string for characters that must be escaped.
40 30 : for ch in input.chars() {
41 30 : if ch == quote_char {
42 2 : num_quotes += 1;
43 28 : } else if ch == '\\' {
44 2 : num_backslashes += 1;
45 26 : }
46 : }
47 :
48 : // Allocate output String.
49 8 : let mut result_size = input.len() + num_quotes + 3; // two quotes, plus a NUL
50 8 : if !as_ident && num_backslashes > 0 {
51 1 : result_size += num_backslashes + 2;
52 7 : }
53 :
54 8 : let mut output = String::with_capacity(result_size);
55 8 :
56 8 : // If we are escaping a literal that contains backslashes, we use
57 8 : // the escape string syntax so that the result is correct under
58 8 : // either value of standard_conforming_strings. We also emit a
59 8 : // leading space in this case, to guard against the possibility
60 8 : // that the result might be interpolated immediately following an
61 8 : // identifier.
62 8 : if !as_ident && num_backslashes > 0 {
63 1 : output.push(' ');
64 1 : output.push('E');
65 7 : }
66 :
67 : // Opening quote.
68 8 : output.push(quote_char);
69 8 :
70 8 : // Use fast path if possible.
71 8 : //
72 8 : // We've already verified that the input string is well-formed in
73 8 : // the current encoding. If it contains no quotes and, in the
74 8 : // case of literal-escaping, no backslashes, then we can just copy
75 8 : // it directly to the output buffer, adding the necessary quotes.
76 8 : //
77 8 : // If not, we must rescan the input and process each character
78 8 : // individually.
79 8 : if num_quotes == 0 && (num_backslashes == 0 || as_ident) {
80 5 : output.push_str(input);
81 5 : } else {
82 12 : for ch in input.chars() {
83 12 : if ch == quote_char || (!as_ident && ch == '\\') {
84 3 : output.push(ch);
85 9 : }
86 12 : output.push(ch);
87 : }
88 : }
89 :
90 8 : output.push(quote_char);
91 8 :
92 8 : output
93 8 : }
|