Skip to content

Commit b5d342e

Browse files
committed
fix(env): Error if values are not valid Unicode
Return an error instead of panicking.
1 parent ef486cf commit b5d342e

File tree

2 files changed

+39
-10
lines changed

2 files changed

+39
-10
lines changed

src/env.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::env;
2+
use std::ffi::OsString;
23

34
#[cfg(feature = "convert-case")]
45
use convert_case::{Case, Casing};
@@ -7,6 +8,7 @@ use crate::error::Result;
78
use crate::map::Map;
89
use crate::source::Source;
910
use crate::value::{Value, ValueKind};
11+
use crate::ConfigError;
1012

1113
/// An environment source collects a dictionary of environment variables values into a hierarchical
1214
/// config Value type. We have to be aware how the config tree is created from the environment
@@ -243,10 +245,16 @@ impl Source for Environment {
243245
.as_ref()
244246
.map(|prefix| format!("{prefix}{prefix_separator}").to_lowercase());
245247

246-
let collector = |(key, value): (String, String)| {
248+
let collector = |(key, value): (OsString, OsString)| {
249+
let key = match key.into_string() {
250+
Ok(key) => key,
251+
// Key is not valid unicode, skip it
252+
Err(_) => return Ok(()),
253+
};
254+
247255
// Treat empty environment variables as unset
248256
if self.ignore_empty && value.is_empty() {
249-
return;
257+
return Ok(());
250258
}
251259

252260
let mut key = key.to_lowercase();
@@ -260,10 +268,18 @@ impl Source for Environment {
260268
}
261269
} else {
262270
// Skip this key
263-
return;
271+
return Ok(());
264272
}
265273
}
266274

275+
// At this point, we don't know if the key is required or not.
276+
// Therefore if the value is not a valid unicode string, we error out.
277+
let value = value.into_string().map_err(|os_string| {
278+
ConfigError::Message(format!(
279+
"env variable {key:?} contains non-Unicode data: {os_string:?}"
280+
))
281+
})?;
282+
267283
// If separator is given replace with `.`
268284
if !separator.is_empty() {
269285
key = key.replace(separator, ".");
@@ -308,12 +324,18 @@ impl Source for Environment {
308324
};
309325

310326
m.insert(key, Value::new(Some(&uri), value));
327+
328+
Ok(())
311329
};
312330

313331
match &self.source {
314-
Some(source) => source.clone().into_iter().for_each(collector),
315-
None => env::vars().for_each(collector),
316-
}
332+
Some(source) => source
333+
.clone()
334+
.into_iter()
335+
.map(|(key, value)| (key.into(), value.into()))
336+
.try_for_each(collector),
337+
None => env::vars_os().try_for_each(collector),
338+
}?;
317339

318340
Ok(m)
319341
}

tests/testsuite/env.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,6 @@ mod unicode_tests {
751751
}
752752

753753
#[test]
754-
#[should_panic(expected = r#"`Result::unwrap()` on an `Err` value: "\xFF""#)]
755754
fn test_invalid_unicode_key_ignored() {
756755
temp_env::with_vars(
757756
vec![
@@ -767,7 +766,6 @@ mod unicode_tests {
767766
}
768767

769768
#[test]
770-
#[should_panic(expected = r#"`Result::unwrap()` on an `Err` value: "\xFF""#)]
771769
fn test_invalid_unicode_value_filtered() {
772770
temp_env::with_vars(
773771
vec![
@@ -787,12 +785,21 @@ mod unicode_tests {
787785
}
788786

789787
#[test]
790-
#[should_panic(expected = r#"`Result::unwrap()` on an `Err` value: "\xFF""#)]
791788
fn test_invalid_unicode_value_not_filtered() {
792789
temp_env::with_vars(
793790
vec![("invalid_value1", Some(make_invalid_unicode_os_string()))],
794791
|| {
795-
Environment::default().collect().unwrap();
792+
let result = Environment::default().collect();
793+
794+
#[cfg(unix)]
795+
let expected =
796+
str![[r#"env variable "invalid_value1" contains non-Unicode data: "/xFF""#]];
797+
#[cfg(windows)]
798+
let expected = str![[
799+
r#"env variable "invalid_value1" contains non-Unicode data: "/u{d800}""#
800+
]];
801+
802+
assert_data_eq!(result.unwrap_err().to_string(), expected);
796803
},
797804
);
798805
}

0 commit comments

Comments
 (0)