@@ -683,17 +683,25 @@ impl Update {
683
683
let install_mode = self . config . install_mode ( ) ;
684
684
let current_args = & self . current_exe_args ( ) [ 1 ..] ;
685
685
let msi_args;
686
+ let nsis_args;
686
687
687
688
let installer_args: Vec < & OsStr > = match & updater_type {
688
- WindowsUpdaterType :: Nsis { .. } => install_mode
689
- . nsis_args ( )
690
- . iter ( )
691
- . map ( OsStr :: new)
692
- . chain ( once ( OsStr :: new ( "/UPDATE" ) ) )
693
- . chain ( once ( OsStr :: new ( "/ARGS" ) ) )
694
- . chain ( current_args. to_vec ( ) )
695
- . chain ( self . installer_args ( ) )
696
- . collect ( ) ,
689
+ WindowsUpdaterType :: Nsis { .. } => {
690
+ nsis_args = current_args
691
+ . iter ( )
692
+ . map ( escape_nsis_current_exe_arg)
693
+ . collect :: < Vec < _ > > ( ) ;
694
+
695
+ install_mode
696
+ . nsis_args ( )
697
+ . iter ( )
698
+ . map ( OsStr :: new)
699
+ . chain ( once ( OsStr :: new ( "/UPDATE" ) ) )
700
+ . chain ( once ( OsStr :: new ( "/ARGS" ) ) )
701
+ . chain ( nsis_args. iter ( ) . map ( OsStr :: new) )
702
+ . chain ( self . installer_args ( ) )
703
+ . collect ( )
704
+ }
697
705
WindowsUpdaterType :: Msi { path, .. } => {
698
706
let escaped_args = current_args
699
707
. iter ( )
@@ -1363,6 +1371,41 @@ impl PathExt for PathBuf {
1363
1371
}
1364
1372
}
1365
1373
1374
+ // adapted from https://github.com/rust-lang/rust/blob/1c047506f94cd2d05228eb992b0a6bbed1942349/library/std/src/sys/args/windows.rs#L174
1375
+ #[ cfg( windows) ]
1376
+ fn escape_nsis_current_exe_arg ( arg : & & OsStr ) -> String {
1377
+ let arg = arg. to_string_lossy ( ) ;
1378
+ let mut cmd: Vec < char > = Vec :: new ( ) ;
1379
+
1380
+ // compared to std we additionally escape `/` so that nsis won't interpret them as a beginning of an nsis argument.
1381
+ let quote = arg. chars ( ) . any ( |c| c == ' ' || c == '\t' || c == '/' ) || arg. is_empty ( ) ;
1382
+ let escape = true ;
1383
+ if quote {
1384
+ cmd. push ( '"' ) ;
1385
+ }
1386
+ let mut backslashes: usize = 0 ;
1387
+ for x in arg. chars ( ) {
1388
+ if escape {
1389
+ if x == '\\' {
1390
+ backslashes += 1 ;
1391
+ } else {
1392
+ if x == '"' {
1393
+ // Add n+1 backslashes to total 2n+1 before internal '"'.
1394
+ cmd. extend ( ( 0 ..=backslashes) . map ( |_| '\\' ) ) ;
1395
+ }
1396
+ backslashes = 0 ;
1397
+ }
1398
+ }
1399
+ cmd. push ( x) ;
1400
+ }
1401
+ if quote {
1402
+ // Add n backslashes to total 2n before ending '"'.
1403
+ cmd. extend ( ( 0 ..backslashes) . map ( |_| '\\' ) ) ;
1404
+ cmd. push ( '"' ) ;
1405
+ }
1406
+ cmd. into_iter ( ) . collect ( )
1407
+ }
1408
+
1366
1409
#[ cfg( windows) ]
1367
1410
fn escape_msi_property_arg ( arg : impl AsRef < OsStr > ) -> String {
1368
1411
let mut arg = arg. as_ref ( ) . to_string_lossy ( ) . to_string ( ) ;
@@ -1375,7 +1418,7 @@ fn escape_msi_property_arg(arg: impl AsRef<OsStr>) -> String {
1375
1418
}
1376
1419
1377
1420
if arg. contains ( '"' ) {
1378
- arg = arg. replace ( '"' , r#""""""# )
1421
+ arg = arg. replace ( '"' , r#""""""# ) ;
1379
1422
}
1380
1423
1381
1424
if arg. starts_with ( '-' ) {
@@ -1406,7 +1449,7 @@ mod tests {
1406
1449
1407
1450
#[ test]
1408
1451
#[ cfg( windows) ]
1409
- fn it_escapes_correctly ( ) {
1452
+ fn it_escapes_correctly_for_msi ( ) {
1410
1453
use crate :: updater:: escape_msi_property_arg;
1411
1454
1412
1455
// Explanation for quotes:
@@ -1451,4 +1494,47 @@ mod tests {
1451
1494
assert_eq ! ( escape_msi_property_arg( orig) , escaped) ;
1452
1495
}
1453
1496
}
1497
+
1498
+ #[ test]
1499
+ #[ cfg( windows) ]
1500
+ fn it_escapes_correctly_for_nsis ( ) {
1501
+ use crate :: updater:: escape_nsis_current_exe_arg;
1502
+ use std:: ffi:: OsStr ;
1503
+
1504
+ let cases = [
1505
+ "something" ,
1506
+ "--flag" ,
1507
+ "--empty=" ,
1508
+ "--arg=value" ,
1509
+ "some space" , // This simulates `./my-app "some string"`.
1510
+ "--arg value" , // -> This simulates `./my-app "--arg value"`. Same as above but it triggers the startsWith(`-`) logic.
1511
+ "--arg=unwrapped space" , // `./my-app --arg="unwrapped space"`
1512
+ "--arg=\" wrapped\" " , // `./my-app --args=""wrapped""`
1513
+ "--arg=\" wrapped space\" " , // `./my-app --args=""wrapped space""`
1514
+ "--arg=midword\" wrapped space\" " , // `./my-app --args=midword""wrapped""`
1515
+ "" , // `./my-app '""'`
1516
+ ] ;
1517
+ // Note: These may not be the results we actually want (monitor this!).
1518
+ // We only make sure the implementation doesn't unintentionally change.
1519
+ let cases_escaped = [
1520
+ "something" ,
1521
+ "--flag" ,
1522
+ "--empty=" ,
1523
+ "--arg=value" ,
1524
+ "\" some space\" " ,
1525
+ "\" --arg value\" " ,
1526
+ "\" --arg=unwrapped space\" " ,
1527
+ "--arg=\\ \" wrapped\\ \" " ,
1528
+ "\" --arg=\\ \" wrapped space\\ \" \" " ,
1529
+ "\" --arg=midword\\ \" wrapped space\\ \" \" " ,
1530
+ "\" \" " ,
1531
+ ] ;
1532
+
1533
+ // Just to be sure we didn't mess that up
1534
+ assert_eq ! ( cases. len( ) , cases_escaped. len( ) ) ;
1535
+
1536
+ for ( orig, escaped) in cases. iter ( ) . zip ( cases_escaped) {
1537
+ assert_eq ! ( escape_nsis_current_exe_arg( & OsStr :: new( orig) ) , escaped) ;
1538
+ }
1539
+ }
1454
1540
}
0 commit comments