1use std::{env, fmt};
8
9use adotenv::load::LoadedEnvironment;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum ApplyError {
15 EmptyKey,
17 InvalidKey {
19 key: String,
21 },
22 InvalidValue {
24 key: String,
26 },
27}
28
29impl fmt::Display for ApplyError {
30 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::EmptyKey => formatter.write_str("environment key is empty"),
33 Self::InvalidKey { key } => {
34 write!(formatter, "environment key contains NUL or '=': {key:?}")
35 }
36 Self::InvalidValue { key } => {
37 write!(formatter, "environment value for {key:?} contains NUL")
38 }
39 }
40 }
41}
42
43impl std::error::Error for ApplyError {}
44
45pub fn validate(environment: &LoadedEnvironment) -> Result<(), ApplyError> {
51 for (key, value) in environment.values() {
52 if key.is_empty() {
53 return Err(ApplyError::EmptyKey);
54 }
55 if key.bytes().any(|byte| byte == 0 || byte == b'=') {
56 return Err(ApplyError::InvalidKey {
57 key: key.to_string(),
58 });
59 }
60 if value.as_bytes().contains(&0) {
61 return Err(ApplyError::InvalidValue {
62 key: key.to_string(),
63 });
64 }
65 }
66 Ok(())
67}
68
69pub unsafe fn apply(environment: &LoadedEnvironment) -> Result<(), ApplyError> {
85 validate(environment)?;
86 for (key, value) in environment.values() {
87 unsafe { env::set_var(key, value) };
88 }
89 Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94 use std::{
95 fs,
96 path::PathBuf,
97 sync::atomic::{AtomicU64, Ordering},
98 };
99
100 use super::*;
101 use adotenv::load::load_cascade;
102
103 static NEXT: AtomicU64 = AtomicU64::new(0);
104
105 struct TempDir(PathBuf);
106
107 impl TempDir {
108 fn new() -> Self {
109 let path = std::env::temp_dir().join(format!(
110 "adotenv-process-{}-{}",
111 std::process::id(),
112 NEXT.fetch_add(1, Ordering::Relaxed)
113 ));
114 fs::create_dir_all(&path).unwrap();
115 Self(path)
116 }
117 fn path(&self) -> &std::path::Path {
118 &self.0
119 }
120 }
121
122 impl Drop for TempDir {
123 fn drop(&mut self) {
124 let _ = fs::remove_dir_all(&self.0);
125 }
126 }
127
128 fn environment(key: &str, value: &str) -> (TempDir, LoadedEnvironment) {
129 let dir = TempDir::new();
130 fs::write(dir.path().join(".env"), format!("{key}={value}\n")).unwrap();
131 let loaded = load_cascade(dir.path(), "").unwrap();
132 (dir, loaded)
133 }
134
135 #[test]
136 fn apply_sets_loaded_values_and_can_be_removed() {
137 let key = format!(
138 "ADOTENV_PROCESS_{}_{}",
139 std::process::id(),
140 NEXT.fetch_add(1, Ordering::Relaxed)
141 );
142 let (_dir, loaded) = environment(&key, "value");
143 unsafe { apply(&loaded).unwrap() };
144 assert_eq!(std::env::var(&key).as_deref(), Ok("value"));
145 std::env::remove_var(&key);
146 }
147
148 #[test]
149 fn validation_rejects_nul_values_before_mutation() {
150 let key = format!(
151 "ADOTENV_PROCESS_BAD_{}_{}",
152 std::process::id(),
153 NEXT.fetch_add(1, Ordering::Relaxed)
154 );
155 let (_dir, loaded) = environment(&key, "bad\0value");
156 assert!(matches!(
157 validate(&loaded),
158 Err(ApplyError::InvalidValue { .. })
159 ));
160 assert!(matches!(
161 unsafe { apply(&loaded) },
162 Err(ApplyError::InvalidValue { .. })
163 ));
164 assert!(std::env::var_os(&key).is_none());
165 }
166
167 #[test]
168 fn error_messages_include_the_key() {
169 let error = ApplyError::InvalidKey { key: "A=B".into() };
170 assert!(error.to_string().contains("A=B"));
171 }
172}