Skip to content

Commit 569ff87

Browse files
committed
Properly escape table and column names in prepare_copy_in
We have to assemble queries by hand here which is a bit sketchy. Manually escaping the individual identifiers to avoid introducing injection vulernabilities is unfortunate but necessary.
1 parent 7d11a05 commit 569ff87

File tree

3 files changed

+33
-7
lines changed

3 files changed

+33
-7
lines changed

src/lib.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,15 +763,18 @@ impl InnerConnection {
763763
-> Result<CopyInStatement<'a>> {
764764
let mut query = vec![];
765765
let _ = write!(&mut query, "SELECT ");
766-
let _ = util::comma_join(&mut query, rows.iter().cloned());
767-
let _ = write!(&mut query, " FROM {}", table);
766+
let _ = util::comma_join_quoted_idents(&mut query, rows.iter().cloned());
767+
let _ = write!(&mut query, " FROM ");
768+
let _ = util::write_quoted_ident(&mut query, table);
768769
let query = String::from_utf8(query).unwrap();
769770
let (_, columns) = try!(self.raw_prepare("", &query));
770771
let column_types = columns.into_iter().map(|desc| desc.type_).collect();
771772

772773
let mut query = vec![];
773-
let _ = write!(&mut query, "COPY {} (", table);
774-
let _ = util::comma_join(&mut query, rows.iter().cloned());
774+
let _ = write!(&mut query, "COPY ");
775+
let _ = util::write_quoted_ident(&mut query, table);
776+
let _ = write!(&mut query, " (");
777+
let _ = util::comma_join_quoted_idents(&mut query, rows.iter().cloned());
775778
let _ = write!(&mut query, ") FROM STDIN WITH (FORMAT binary)");
776779
let query = String::from_utf8(query).unwrap();
777780
let stmt_name = self.make_stmt_name();

src/util.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
use std::io;
22
use std::io::prelude::*;
3+
use std::ascii::AsciiExt;
34

4-
pub fn comma_join<'a, W, I>(writer: &mut W, strs: I) -> io::Result<()>
5+
pub fn comma_join_quoted_idents<'a, W, I>(writer: &mut W, strs: I) -> io::Result<()>
56
where W: Write, I: Iterator<Item=&'a str> {
67
let mut first = true;
78
for str_ in strs {
89
if !first {
910
try!(write!(writer, ", "));
1011
}
1112
first = false;
12-
try!(write!(writer, "{}", str_));
13+
try!(write_quoted_ident(writer, str_));
1314
}
1415
Ok(())
1516
}
1617

18+
// See http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html for ident grammar
19+
pub fn write_quoted_ident<W: Write>(w: &mut W, ident: &str) -> io::Result<()> {
20+
try!(write!(w, "U&\""));
21+
for ch in ident.chars() {
22+
match ch {
23+
'"' => try!(write!(w, "\"\"")),
24+
'\\' => try!(write!(w, "\\\\")),
25+
ch if ch.is_ascii() => try!(write!(w, "{}", ch)),
26+
ch => try!(write!(w, "\\+{:06X}", ch as u32)),
27+
}
28+
}
29+
write!(w, "\"")
30+
}
31+
1732
pub fn parse_update_count(tag: String) -> u64 {
1833
tag.split(' ').last().unwrap().parse().unwrap_or(0)
1934
}

tests/test.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ macro_rules! or_panic {
3232
($e:expr) => (
3333
match $e {
3434
Ok(ok) => ok,
35-
Err(err) => panic!("{:?}", err)
35+
Err(err) => panic!("{:#?}", err)
3636
}
3737
)
3838
}
@@ -832,6 +832,14 @@ fn test_copy_in_bad_type() {
832832
or_panic!(conn.execute("SELECT 1", &[]));
833833
}
834834

835+
#[test]
836+
fn test_copy_in_weird_names() {
837+
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None));
838+
or_panic!(conn.execute(r#"CREATE TEMPORARY TABLE "na""me" (U&" \\\+01F4A9" VARCHAR)"#, &[]));
839+
let stmt = or_panic!(conn.prepare_copy_in("na\"me", &[" \\💩"]));
840+
assert_eq!(&Type::Varchar, &stmt.column_types()[0]);
841+
}
842+
835843
#[test]
836844
fn test_batch_execute_copy_from_err() {
837845
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None));

0 commit comments

Comments
 (0)