Skip to content

Commit d1c200e

Browse files
committed
printenv skips environment variables with invalid UTF-8
closes: uutils#9701
1 parent 45f81bb commit d1c200e

File tree

2 files changed

+72
-7
lines changed

2 files changed

+72
-7
lines changed

src/uu/printenv/src/printenv.rs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55

66
use clap::{Arg, ArgAction, Command};
77
use std::env;
8+
#[cfg(unix)]
9+
use std::io::{self, Write};
810
use uucore::translate;
911
use uucore::{error::UResult, format_usage};
1012

13+
#[cfg(unix)]
14+
use std::os::unix::ffi::OsStrExt;
15+
1116
static OPT_NULL: &str = "null";
1217

1318
static ARG_VARIABLES: &str = "variables";
@@ -21,15 +26,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
2126
.map(|v| v.map(ToString::to_string).collect())
2227
.unwrap_or_default();
2328

24-
let separator = if matches.get_flag(OPT_NULL) {
25-
"\x00"
29+
let separator: &[u8] = if matches.get_flag(OPT_NULL) {
30+
b"\x00"
2631
} else {
27-
"\n"
32+
b"\n"
2833
};
2934

35+
#[cfg(unix)]
36+
let mut stdout = io::stdout().lock();
37+
3038
if variables.is_empty() {
31-
for (env_var, value) in env::vars() {
32-
print!("{env_var}={value}{separator}");
39+
for (env_var, value) in env::vars_os() {
40+
#[cfg(unix)]
41+
{
42+
stdout.write_all(env_var.as_bytes())?;
43+
stdout.write_all(b"=")?;
44+
stdout.write_all(value.as_bytes())?;
45+
stdout.write_all(separator)?;
46+
}
47+
#[cfg(not(unix))]
48+
{
49+
// On non-Unix, use lossy conversion as OsStrExt is not available
50+
print!(
51+
"{}={}{}",
52+
env_var.to_string_lossy(),
53+
value.to_string_lossy(),
54+
String::from_utf8_lossy(separator)
55+
);
56+
}
3357
}
3458
return Ok(());
3559
}
@@ -41,8 +65,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4165
error_found = true;
4266
continue;
4367
}
44-
if let Ok(var) = env::var(env_var) {
45-
print!("{var}{separator}");
68+
if let Some(var) = env::var_os(&env_var) {
69+
#[cfg(unix)]
70+
{
71+
stdout.write_all(var.as_bytes())?;
72+
stdout.write_all(separator)?;
73+
}
74+
#[cfg(not(unix))]
75+
{
76+
print!(
77+
"{}{}",
78+
var.to_string_lossy(),
79+
String::from_utf8_lossy(separator)
80+
);
81+
}
4682
} else {
4783
error_found = true;
4884
}

tests/by-util/test_printenv.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5+
#[cfg(unix)]
6+
use std::ffi::OsString;
7+
#[cfg(unix)]
8+
use std::os::unix::ffi::OsStringExt;
59
use uutests::new_ucmd;
610

711
#[test]
@@ -90,3 +94,28 @@ fn test_null_separator() {
9094
.stdout_is("FOO\x00VALUE\x00");
9195
}
9296
}
97+
98+
#[test]
99+
#[cfg(unix)]
100+
#[cfg(not(any(target_os = "freebsd", target_os = "android", target_os = "openbsd")))]
101+
fn test_non_utf8_value() {
102+
// Environment variable values can contain non-UTF-8 bytes on Unix.
103+
// printenv should output them correctly, matching GNU behavior.
104+
// Reproduces: LD_PRELOAD=$'/tmp/lib.so\xff' printenv LD_PRELOAD
105+
let value_with_invalid_utf8 = OsString::from_vec(b"/tmp/lib.so\xff".to_vec());
106+
107+
let result = new_ucmd!()
108+
.env("LD_PRELOAD", &value_with_invalid_utf8)
109+
.arg("LD_PRELOAD")
110+
.run();
111+
112+
// Use byte-based assertions to avoid UTF-8 conversion issues
113+
// when the test framework tries to format error messages
114+
assert!(
115+
result.succeeded(),
116+
"Command failed with exit code: {:?}, stderr: {:?}",
117+
result.code(),
118+
String::from_utf8_lossy(result.stderr())
119+
);
120+
result.stdout_is_bytes(b"/tmp/lib.so\xff\n");
121+
}

0 commit comments

Comments
 (0)