1
1
use std:: borrow:: Cow ;
2
2
use std:: env:: var;
3
- use std:: fmt:: { Display , Write } ;
3
+ use std:: fmt:: { self , Display , Write } ;
4
4
use std:: path:: { Path , PathBuf } ;
5
5
6
6
pub use ssl_mode:: PgSslMode ;
@@ -416,6 +416,9 @@ impl PgConnectOptions {
416
416
417
417
/// Set additional startup options for the connection as a list of key-value pairs.
418
418
///
419
+ /// Escapes the options’ backslash and space characters as per
420
+ /// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
421
+ ///
419
422
/// # Example
420
423
///
421
424
/// ```rust
@@ -436,7 +439,8 @@ impl PgConnectOptions {
436
439
options_str. push ( ' ' ) ;
437
440
}
438
441
439
- write ! ( options_str, "-c {k}={v}" ) . expect ( "failed to write an option to the string" ) ;
442
+ options_str. push_str ( "-c " ) ;
443
+ write ! ( PgOptionsWriteEscaped ( options_str) , "{k}={v}" ) . ok ( ) ;
440
444
}
441
445
self
442
446
}
@@ -590,6 +594,39 @@ fn default_host(port: u16) -> String {
590
594
"localhost" . to_owned ( )
591
595
}
592
596
597
+ /// Writer that escapes passed-in PostgreSQL options.
598
+ ///
599
+ /// Escapes backslashes and spaces with an additional backslash according to
600
+ /// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
601
+ #[ derive( Debug ) ]
602
+ struct PgOptionsWriteEscaped < ' a > ( & ' a mut String ) ;
603
+
604
+ impl Write for PgOptionsWriteEscaped < ' _ > {
605
+ fn write_str ( & mut self , s : & str ) -> fmt:: Result {
606
+ let mut span_start = 0 ;
607
+
608
+ for ( span_end, matched) in s. match_indices ( [ ' ' , '\\' ] ) {
609
+ write ! ( self . 0 , r"{}\{matched}" , & s[ span_start..span_end] ) ?;
610
+ span_start = span_end + matched. len ( ) ;
611
+ }
612
+
613
+ // Write the rest of the string after the last match, or all of it if no matches
614
+ self . 0 . push_str ( & s[ span_start..] ) ;
615
+
616
+ Ok ( ( ) )
617
+ }
618
+
619
+ fn write_char ( & mut self , ch : char ) -> fmt:: Result {
620
+ if matches ! ( ch, ' ' | '\\' ) {
621
+ self . 0 . push ( '\\' ) ;
622
+ }
623
+
624
+ self . 0 . push ( ch) ;
625
+
626
+ Ok ( ( ) )
627
+ }
628
+ }
629
+
593
630
#[ test]
594
631
fn test_options_formatting ( ) {
595
632
let options = PgConnectOptions :: new ( ) . options ( [ ( "geqo" , "off" ) ] ) ;
@@ -604,6 +641,26 @@ fn test_options_formatting() {
604
641
options. options,
605
642
Some ( "-c geqo=off -c statement_timeout=5min" . to_string( ) )
606
643
) ;
644
+ // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
645
+ let options =
646
+ PgConnectOptions :: new ( ) . options ( [ ( "application_name" , r"/back\slash/ and\ spaces" ) ] ) ;
647
+ assert_eq ! (
648
+ options. options,
649
+ Some ( r"-c application_name=/back\\slash/\ and\\\ spaces" . to_string( ) )
650
+ ) ;
607
651
let options = PgConnectOptions :: new ( ) ;
608
652
assert_eq ! ( options. options, None ) ;
609
653
}
654
+
655
+ #[ test]
656
+ fn test_pg_write_escaped ( ) {
657
+ let mut buf = String :: new ( ) ;
658
+ let mut x = PgOptionsWriteEscaped ( & mut buf) ;
659
+ x. write_str ( "x" ) . unwrap ( ) ;
660
+ x. write_str ( "" ) . unwrap ( ) ;
661
+ x. write_char ( '\\' ) . unwrap ( ) ;
662
+ x. write_str ( "y \\ " ) . unwrap ( ) ;
663
+ x. write_char ( ' ' ) . unwrap ( ) ;
664
+ x. write_char ( 'z' ) . unwrap ( ) ;
665
+ assert_eq ! ( buf, r"x\\y\ \\\ z" ) ;
666
+ }
0 commit comments