Skip to content

Commit 7fac19d

Browse files
committed
Handle PATHs with non-unicodes values on Windows
1 parent 019b9b4 commit 7fac19d

File tree

4 files changed

+88
-83
lines changed

4 files changed

+88
-83
lines changed

src/cli/self_update/test.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,34 @@ use std::sync::Mutex;
55
use lazy_static::lazy_static;
66
#[cfg(not(unix))]
77
use winreg::{
8-
enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE},
8+
enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE},
99
RegKey, RegValue,
1010
};
1111

1212
#[cfg(not(unix))]
13-
use crate::utils::utils;
14-
15-
#[cfg(not(unix))]
16-
pub fn get_path() -> Option<String> {
13+
pub fn get_path() -> Option<RegValue> {
1714
let root = RegKey::predef(HKEY_CURRENT_USER);
1815
let environment = root
1916
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
2017
.unwrap();
21-
// XXX: copied from the mock support crate, but I am suspicous of this
22-
// code: This uses ok to allow signalling None for 'delete', but this
23-
// can fail e.g. with !(winerror::ERROR_BAD_FILE_TYPE) or other
24-
// failures; which will lead to attempting to delete the users path
25-
// rather than aborting the test suite.
26-
environment.get_value("PATH").ok()
18+
match environment.get_raw_value("PATH") {
19+
Ok(val) => Some(val),
20+
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => None,
21+
Err(e) => panic!(
22+
"Error getting PATH: {}\nBetter abort to avoid trahsing it.",
23+
e
24+
),
25+
}
2726
}
2827

2928
#[cfg(not(unix))]
30-
fn restore_path(p: Option<String>) {
29+
fn restore_path(p: Option<RegValue>) {
3130
let root = RegKey::predef(HKEY_CURRENT_USER);
3231
let environment = root
3332
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
3433
.unwrap();
3534
if let Some(p) = p.as_ref() {
36-
let reg_value = RegValue {
37-
bytes: utils::string_to_winreg_bytes(&p),
38-
vtype: RegType::REG_EXPAND_SZ,
39-
};
40-
environment.set_raw_value("PATH", &reg_value).unwrap();
35+
environment.set_raw_value("PATH", &p).unwrap();
4136
} else {
4237
let _ = environment.delete_value("PATH");
4338
}
@@ -60,9 +55,9 @@ pub fn with_saved_path(f: &dyn Fn()) {
6055
}
6156

6257
#[cfg(unix)]
63-
pub fn get_path() -> Option<String> {
58+
pub fn get_path() -> Option<()> {
6459
None
6560
}
6661

6762
#[cfg(unix)]
68-
fn restore_path(_: Option<String>) {}
63+
fn restore_path(_: Option<()>) {}

src/cli/self_update/windows.rs

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ pub fn do_add_to_path() -> Result<()> {
144144
_apply_new_path(new_path)
145145
}
146146

147-
fn _apply_new_path(new_path: Option<String>) -> Result<()> {
147+
fn _apply_new_path(new_path: Option<Vec<u16>>) -> Result<()> {
148148
use std::ptr;
149149
use winapi::shared::minwindef::*;
150150
use winapi::um::winuser::{
@@ -169,7 +169,7 @@ fn _apply_new_path(new_path: Option<String>) -> Result<()> {
169169
.chain_err(|| ErrorKind::PermissionDenied)?;
170170
} else {
171171
let reg_value = RegValue {
172-
bytes: utils::string_to_winreg_bytes(&new_path),
172+
bytes: utils::string_to_winreg_bytes(new_path),
173173
vtype: RegType::REG_EXPAND_SZ,
174174
};
175175
environment
@@ -194,9 +194,9 @@ fn _apply_new_path(new_path: Option<String>) -> Result<()> {
194194
}
195195

196196
// Get the windows PATH variable out of the registry as a String. If
197-
// this returns None then the PATH variable is not unicode and we
197+
// this returns None then the PATH variable is not a string and we
198198
// should not mess with it.
199-
fn get_windows_path_var() -> Result<Option<String>> {
199+
fn get_windows_path_var() -> Result<Option<Vec<u16>>> {
200200
use std::io;
201201
use winreg::enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
202202
use winreg::RegKey;
@@ -212,63 +212,72 @@ fn get_windows_path_var() -> Result<Option<String>> {
212212
if let Some(s) = utils::string_from_winreg_value(&val) {
213213
Ok(Some(s))
214214
} else {
215-
warn!("the registry key HKEY_CURRENT_USER\\Environment\\PATH does not contain valid Unicode. \
216-
Not modifying the PATH variable");
215+
warn!(
216+
"the registry key HKEY_CURRENT_USER\\Environment\\PATH is not a string. \
217+
Not modifying the PATH variable"
218+
);
217219
Ok(None)
218220
}
219221
}
220-
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(Some(String::new())),
222+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(Some(Vec::new())),
221223
Err(e) => Err(e).chain_err(|| ErrorKind::WindowsUninstallMadness),
222224
}
223225
}
224226

225227
// Returns None if the existing old_path does not need changing, otherwise
226228
// prepends the path_str to old_path, handling empty old_path appropriately.
227-
fn _add_to_path(old_path: &str, path_str: String) -> Option<String> {
229+
fn _add_to_path(old_path: Vec<u16>, path_str: Vec<u16>) -> Option<Vec<u16>> {
228230
if old_path.is_empty() {
229231
Some(path_str)
230-
} else if old_path.contains(&path_str) {
232+
} else if old_path
233+
.windows(path_str.len())
234+
.any(|path| path == path_str)
235+
{
231236
None
232237
} else {
233238
let mut new_path = path_str;
234-
new_path.push_str(";");
235-
new_path.push_str(&old_path);
239+
new_path.push(b';' as u16);
240+
new_path.extend_from_slice(&old_path);
236241
Some(new_path)
237242
}
238243
}
239244

240245
// Returns None if the existing old_path does not need changing
241-
fn _remove_from_path(old_path: &str, path_str: String) -> Option<String> {
242-
let idx = old_path.find(&path_str)?;
246+
fn _remove_from_path(old_path: Vec<u16>, path_str: Vec<u16>) -> Option<Vec<u16>> {
247+
let idx = old_path
248+
.windows(path_str.len())
249+
.position(|path| path == path_str)?;
243250
// If there's a trailing semicolon (likely, since we probably added one
244251
// during install), include that in the substring to remove. We don't search
245252
// for that to find the string, because if its the last string in the path,
246253
// there may not be.
247254
let mut len = path_str.len();
248-
if old_path.as_bytes().get(idx + path_str.len()) == Some(&b';') {
255+
if old_path.get(idx + path_str.len()) == Some(&(b';' as u16)) {
249256
len += 1;
250257
}
251258

252-
let mut new_path = old_path[..idx].to_string();
253-
new_path.push_str(&old_path[idx + len..]);
259+
let mut new_path = old_path[..idx].to_owned();
260+
new_path.extend_from_slice(&old_path[idx + len..]);
254261
// Don't leave a trailing ; though, we don't want an empty string in the
255262
// path.
256-
if new_path.ends_with(';') {
263+
if new_path.last() == Some(&(b';' as u16)) {
257264
new_path.pop();
258265
}
259266
Some(new_path)
260267
}
261268

262-
fn _with_path_cargo_home_bin<F>(f: F) -> Result<Option<String>>
269+
fn _with_path_cargo_home_bin<F>(f: F) -> Result<Option<Vec<u16>>>
263270
where
264-
F: FnOnce(&str, String) -> Option<String>,
271+
F: FnOnce(Vec<u16>, Vec<u16>) -> Option<Vec<u16>>,
265272
{
273+
use std::ffi::OsString;
274+
use std::os::windows::ffi::OsStrExt;
275+
266276
let windows_path = get_windows_path_var()?;
267-
let path_str = utils::cargo_home()?
268-
.join("bin")
269-
.to_string_lossy()
270-
.into_owned();
271-
Ok(windows_path.and_then(|old_path| f(&old_path, path_str)))
277+
let mut path_str = utils::cargo_home()?;
278+
path_str.push("bin");
279+
Ok(windows_path
280+
.and_then(|old_path| f(old_path, OsString::from(path_str).encode_wide().collect())))
272281
}
273282

274283
pub fn do_remove_from_path() -> Result<()> {
@@ -402,26 +411,33 @@ pub fn delete_rustup_and_cargo_home() -> Result<()> {
402411

403412
#[cfg(test)]
404413
mod tests {
414+
use std::ffi::OsString;
415+
use std::os::windows::ffi::OsStrExt;
416+
405417
use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
406418
use winreg::{RegKey, RegValue};
407419

408420
use crate::currentprocess;
409421
use crate::test::with_saved_path;
410422
use crate::utils::utils;
411423

424+
fn wide(str: &str) -> Vec<u16> {
425+
OsString::from(str).encode_wide().collect()
426+
}
427+
412428
#[test]
413429
fn windows_install_does_not_add_path_twice() {
414430
assert_eq!(
415431
None,
416432
super::_add_to_path(
417-
r"c:\users\example\.cargo\bin;foo",
418-
r"c:\users\example\.cargo\bin".into()
433+
wide(r"c:\users\example\.cargo\bin;foo"),
434+
wide(r"c:\users\example\.cargo\bin")
419435
)
420436
);
421437
}
422438

423439
#[test]
424-
fn windows_doesnt_mess_with_a_non_unicode_path() {
440+
fn windows_doesnt_mess_with_a_non_string_path() {
425441
// This writes an error, so we want a sink for it.
426442
let tp = Box::new(currentprocess::TestProcess {
427443
vars: [("HOME".to_string(), "/unused".to_string())]
@@ -437,23 +453,19 @@ mod tests {
437453
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
438454
.unwrap();
439455
let reg_value = RegValue {
440-
bytes: vec![
441-
0x00, 0xD8, // leading surrogate
442-
0x01, 0x01, // bogus trailing surrogate
443-
0x00, 0x00,
444-
], // null
445-
vtype: RegType::REG_EXPAND_SZ,
456+
bytes: vec![0x00, 0xD8, 0x01, 0x01, 0x00, 0x00],
457+
vtype: RegType::REG_BINARY,
446458
};
447459
environment.set_raw_value("PATH", &reg_value).unwrap();
448460
// Ok(None) signals no change to the PATH setting layer
449-
fn panic(_: &str, _: String) -> Option<String> {
450-
panic!("called");
451-
}
452-
assert_eq!(None, super::_with_path_cargo_home_bin(panic).unwrap());
461+
assert_eq!(
462+
None,
463+
super::_with_path_cargo_home_bin(|_, _| panic!("called")).unwrap()
464+
);
453465
})
454466
});
455467
assert_eq!(
456-
r"warning: the registry key HKEY_CURRENT_USER\Environment\PATH does not contain valid Unicode. Not modifying the PATH variable
468+
r"warning: the registry key HKEY_CURRENT_USER\Environment\PATH is not a string. Not modifying the PATH variable
457469
",
458470
String::from_utf8(tp.get_stderr()).unwrap()
459471
);
@@ -474,15 +486,15 @@ mod tests {
474486
{
475487
// Can't compare the Results as Eq isn't derived; thanks error-chain.
476488
#![allow(clippy::unit_cmp)]
477-
assert_eq!((), super::_apply_new_path(Some("foo".into())).unwrap());
489+
assert_eq!((), super::_apply_new_path(Some(wide("foo"))).unwrap());
478490
}
479491
let root = RegKey::predef(HKEY_CURRENT_USER);
480492
let environment = root
481493
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
482494
.unwrap();
483495
let path = environment.get_raw_value("PATH").unwrap();
484496
assert_eq!(path.vtype, RegType::REG_EXPAND_SZ);
485-
assert_eq!(utils::string_to_winreg_bytes("foo"), &path.bytes[..]);
497+
assert_eq!(utils::string_to_winreg_bytes(wide("foo")), &path.bytes[..]);
486498
})
487499
});
488500
}
@@ -503,7 +515,7 @@ mod tests {
503515
.set_raw_value(
504516
"PATH",
505517
&RegValue {
506-
bytes: utils::string_to_winreg_bytes("foo"),
518+
bytes: utils::string_to_winreg_bytes(wide("foo")),
507519
vtype: RegType::REG_EXPAND_SZ,
508520
},
509521
)
@@ -512,7 +524,7 @@ mod tests {
512524
{
513525
// Can't compare the Results as Eq isn't derived; thanks error-chain.
514526
#![allow(clippy::unit_cmp)]
515-
assert_eq!((), super::_apply_new_path(Some("".into())).unwrap());
527+
assert_eq!((), super::_apply_new_path(Some(Vec::new())).unwrap());
516528
}
517529
let reg_value = environment.get_raw_value("PATH");
518530
match reg_value {
@@ -536,18 +548,18 @@ mod tests {
536548
.unwrap();
537549
environment.delete_value("PATH").unwrap();
538550

539-
assert_eq!(Some("".into()), super::get_windows_path_var().unwrap());
551+
assert_eq!(Some(Vec::new()), super::get_windows_path_var().unwrap());
540552
})
541553
});
542554
}
543555

544556
#[test]
545557
fn windows_uninstall_removes_semicolon_from_path_prefix() {
546558
assert_eq!(
547-
"foo",
559+
wide("foo"),
548560
super::_remove_from_path(
549-
r"c:\users\example\.cargo\bin;foo",
550-
r"c:\users\example\.cargo\bin".into()
561+
wide(r"c:\users\example\.cargo\bin;foo"),
562+
wide(r"c:\users\example\.cargo\bin"),
551563
)
552564
.unwrap()
553565
)
@@ -556,10 +568,10 @@ mod tests {
556568
#[test]
557569
fn windows_uninstall_removes_semicolon_from_path_suffix() {
558570
assert_eq!(
559-
"foo",
571+
wide("foo"),
560572
super::_remove_from_path(
561-
r"foo;c:\users\example\.cargo\bin",
562-
r"c:\users\example\.cargo\bin".into()
573+
wide(r"foo;c:\users\example\.cargo\bin"),
574+
wide(r"c:\users\example\.cargo\bin"),
563575
)
564576
.unwrap()
565577
)

src/utils/utils.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::cmp::Ord;
22
use std::env;
3-
use std::ffi::OsString;
43
use std::fs::{self, File};
54
use std::io::{self, BufReader, Write};
65
use std::path::{Path, PathBuf};
@@ -275,6 +274,8 @@ pub fn parse_url(url: &str) -> Result<Url> {
275274
}
276275

277276
pub fn cmd_status(name: &'static str, cmd: &mut Command) -> Result<()> {
277+
use std::ffi::OsString;
278+
278279
raw::cmd_status(cmd).chain_err(|| ErrorKind::RunningCommand {
279280
name: OsString::from(name),
280281
})
@@ -538,10 +539,8 @@ pub fn format_path_for_display(path: &str) -> String {
538539

539540
/// Encodes a utf-8 string as a null-terminated UCS-2 string in bytes
540541
#[cfg(windows)]
541-
pub fn string_to_winreg_bytes(s: &str) -> Vec<u8> {
542-
use std::ffi::OsStr;
543-
use std::os::windows::ffi::OsStrExt;
544-
let v: Vec<u16> = OsStr::new(s).encode_wide().chain(Some(0)).collect();
542+
pub fn string_to_winreg_bytes(mut v: Vec<u16>) -> Vec<u8> {
543+
v.push(0);
545544
unsafe { std::slice::from_raw_parts(v.as_ptr().cast::<u8>(), v.len() * 2).to_vec() }
546545
}
547546

@@ -550,23 +549,22 @@ pub fn string_to_winreg_bytes(s: &str) -> Vec<u8> {
550549
// returns null. The winreg library itself does a lossy unicode
551550
// conversion.
552551
#[cfg(windows)]
553-
pub fn string_from_winreg_value(val: &winreg::RegValue) -> Option<String> {
552+
pub fn string_from_winreg_value(val: &winreg::RegValue) -> Option<Vec<u16>> {
554553
use std::slice;
555554
use winreg::enums::RegType;
556555

557556
match val.vtype {
558557
RegType::REG_SZ | RegType::REG_EXPAND_SZ => {
559558
// Copied from winreg
560-
let words = unsafe {
559+
let mut words = unsafe {
561560
#[allow(clippy::cast_ptr_alignment)]
562561
slice::from_raw_parts(val.bytes.as_ptr().cast::<u16>(), val.bytes.len() / 2)
562+
.to_owned()
563563
};
564-
String::from_utf16(words).ok().map(|mut s| {
565-
while s.ends_with('\u{0}') {
566-
s.pop();
567-
}
568-
s
569-
})
564+
while words.last() == Some(&0) {
565+
words.pop();
566+
}
567+
Some(words)
570568
}
571569
_ => None,
572570
}

0 commit comments

Comments
 (0)