1use std::cmp::Ordering;
10use std::collections::{BTreeMap, HashMap};
11use std::ffi::{OsStr, OsString};
12use std::hash::{Hash, Hasher};
13
14#[cfg(unix)]
15use std::os::unix::ffi::OsStrExt;
16#[cfg(windows)]
17use std::os::windows::ffi::OsStrExt;
18
19use wildmatch::WildMatch;
20
21use crate::CommandError;
22use crate::command::contains_nul;
23
24mod model;
25
26use model::{CaseFoldedText, EnvironmentNameIdentity};
27pub use model::{
28 CoreEnvironment, EnvironmentBase, EnvironmentFilterAction, EnvironmentInput,
29 EnvironmentNameKey, EnvironmentOverride, EnvironmentPattern, EnvironmentSpec,
30};
31
32impl PartialEq for EnvironmentPattern {
33 fn eq(&self, other: &Self) -> bool {
34 self.canonical == other.canonical
35 }
36}
37
38impl Eq for EnvironmentPattern {}
39
40impl Hash for EnvironmentPattern {
41 fn hash<H: Hasher>(&self, state: &mut H) {
42 self.canonical.hash(state);
43 }
44}
45
46impl PartialOrd for EnvironmentPattern {
47 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
48 Some(self.cmp(other))
49 }
50}
51
52impl Ord for EnvironmentPattern {
53 fn cmp(&self, other: &Self) -> Ordering {
54 self.canonical.cmp(&other.canonical)
55 }
56}
57
58impl EnvironmentPattern {
59 pub fn new(pattern: impl Into<String>) -> Result<Self, CommandError> {
61 let pattern = pattern.into();
62 if pattern.is_empty() {
63 return Err(CommandError::EmptyEnvironmentPattern);
64 }
65 if contains_nul(OsStr::new(&pattern)) {
66 return Err(CommandError::EnvironmentPatternContainsNul);
67 }
68 if pattern.contains('=') {
69 return Err(CommandError::EnvironmentPatternContainsEquals);
70 }
71 Ok(Self {
72 canonical: case_folded_text(&pattern),
73 matcher: WildMatch::new_case_insensitive(&pattern),
74 original: pattern,
75 })
76 }
77
78 pub fn as_str(&self) -> &str {
84 &self.original
85 }
86
87 pub fn matches(&self, name: &str) -> bool {
89 self.matcher.matches(name)
90 }
91}
92
93impl EnvironmentNameKey {
94 pub fn new(name: &OsStr) -> Self {
96 Self(environment_name_identity(name))
97 }
98}
99
100impl CoreEnvironment {
101 pub fn from_selected<I>(variables: I) -> Result<Self, CommandError>
104 where
105 I: IntoIterator<Item = (OsString, OsString)>,
106 {
107 Ok(Self {
108 variables: collect_environment(variables)?,
109 })
110 }
111
112 pub fn variables(&self) -> &BTreeMap<OsString, OsString> {
114 &self.variables
115 }
116}
117
118impl EnvironmentInput {
119 pub fn all<I>(variables: I) -> Result<Self, CommandError>
121 where
122 I: IntoIterator<Item = (OsString, OsString)>,
123 {
124 Ok(Self {
125 base: EnvironmentBase::All,
126 variables: collect_environment(variables)?,
127 })
128 }
129
130 pub fn core(environment: CoreEnvironment) -> Self {
132 Self {
133 base: EnvironmentBase::Core,
134 variables: environment.variables,
135 }
136 }
137
138 pub fn empty() -> Self {
140 Self {
141 base: EnvironmentBase::None,
142 variables: BTreeMap::new(),
143 }
144 }
145
146 pub const fn base(&self) -> EnvironmentBase {
148 self.base
149 }
150
151 pub fn variables(&self) -> &BTreeMap<OsString, OsString> {
153 &self.variables
154 }
155
156 pub fn into_variables(self) -> BTreeMap<OsString, OsString> {
158 self.variables
159 }
160}
161
162impl PartialEq for EnvironmentSpec {
163 fn eq(&self, other: &Self) -> bool {
164 self.base == other.base
165 && self.filters == other.filters
166 && self.override_names.len() == other.override_names.len()
167 && self.override_names.iter().all(|(key, name)| {
168 let Some(other_name) = other.override_names.get(key) else {
169 return false;
170 };
171 self.overrides.get(name) == other.overrides.get(other_name)
172 })
173 }
174}
175
176impl Eq for EnvironmentSpec {}
177
178impl EnvironmentSpec {
179 pub fn inherit_all() -> Self {
181 Self {
182 base: EnvironmentBase::All,
183 overrides: BTreeMap::new(),
184 override_names: HashMap::new(),
185 filters: BTreeMap::new(),
186 }
187 }
188
189 pub fn empty() -> Self {
191 Self {
192 base: EnvironmentBase::None,
193 overrides: BTreeMap::new(),
194 override_names: HashMap::new(),
195 filters: BTreeMap::new(),
196 }
197 }
198
199 pub fn inherit_core() -> Self {
201 Self {
202 base: EnvironmentBase::Core,
203 overrides: BTreeMap::new(),
204 override_names: HashMap::new(),
205 filters: BTreeMap::new(),
206 }
207 }
208
209 pub fn base(&self) -> EnvironmentBase {
211 self.base
212 }
213
214 pub fn overrides(&self) -> &BTreeMap<OsString, EnvironmentOverride> {
219 &self.overrides
220 }
221
222 pub fn filters(&self) -> &BTreeMap<EnvironmentPattern, EnvironmentFilterAction> {
224 &self.filters
225 }
226
227 pub fn override_for(&self, name: &OsStr) -> Option<&EnvironmentOverride> {
229 self.override_names
230 .get(&EnvironmentNameKey::new(name))
231 .and_then(|name| self.overrides.get(name))
232 }
233
234 pub fn filter_action_for(&self, name: &str) -> Option<EnvironmentFilterAction> {
241 let mut include_matches = false;
242 for (pattern, action) in &self.filters {
243 if pattern.matches(name) {
244 match action {
245 EnvironmentFilterAction::Include => include_matches = true,
246 EnvironmentFilterAction::Exclude => {
247 return Some(EnvironmentFilterAction::Exclude);
248 }
249 }
250 }
251 }
252 include_matches.then_some(EnvironmentFilterAction::Include)
253 }
254
255 pub fn apply_to(&self, input: EnvironmentInput) -> Result<EnvironmentInput, CommandError> {
266 if !base_is_at_most(input.base, self.base) {
267 return Err(CommandError::EnvironmentBaseTooPermissive {
268 required: self.base,
269 supplied: input.base,
270 });
271 }
272 let base = input.base;
273 let variables = input.variables;
274 let mut environment = BTreeMap::new();
275 let mut environment_names = HashMap::new();
276 for (name, value) in variables {
277 remove_environment_name(&mut environment, &mut environment_names, &name);
278 environment_names.insert(EnvironmentNameKey::new(&name), name.clone());
279 environment.insert(name, value);
280 }
281 let has_include_filter = self
282 .filters
283 .values()
284 .any(|action| *action == EnvironmentFilterAction::Include);
285
286 environment.retain(|name, _| {
287 self.filters.is_empty()
288 || name.to_str().is_some_and(|name| {
289 !self.filters.iter().any(|(pattern, action)| {
290 *action == EnvironmentFilterAction::Exclude && pattern.matches(name)
291 })
292 })
293 });
294
295 for (name, value) in &self.overrides {
296 match value {
297 EnvironmentOverride::Set(value) => {
298 remove_environment_name(&mut environment, &mut environment_names, name);
299 environment_names.insert(EnvironmentNameKey::new(name), name.clone());
300 environment.insert(name.clone(), value.clone());
301 }
302 EnvironmentOverride::Remove => {
303 remove_environment_name(&mut environment, &mut environment_names, name);
304 }
305 }
306 }
307
308 if has_include_filter {
309 environment.retain(|name, _| {
310 name.to_str().is_some_and(|name| {
311 self.filters.iter().any(|(pattern, action)| {
312 *action == EnvironmentFilterAction::Include && pattern.matches(name)
313 })
314 })
315 });
316 } else if !self.filters.is_empty() {
317 environment.retain(|name, _| name.to_str().is_some());
318 }
319
320 Ok(EnvironmentInput {
321 base,
322 variables: environment,
323 })
324 }
325
326 pub fn with_var(
328 mut self,
329 name: impl Into<OsString>,
330 value: impl Into<OsString>,
331 ) -> Result<Self, CommandError> {
332 let name = name.into();
333 let value = value.into();
334 validate_name(&name)?;
335 if contains_nul(&value) {
336 return Err(CommandError::EnvironmentValueContainsNul);
337 }
338 remove_environment_name(&mut self.overrides, &mut self.override_names, &name);
339 self.override_names
340 .insert(EnvironmentNameKey::new(&name), name.clone());
341 self.overrides.insert(name, EnvironmentOverride::Set(value));
342 Ok(self)
343 }
344
345 pub fn without_var(mut self, name: impl Into<OsString>) -> Result<Self, CommandError> {
347 let name = name.into();
348 validate_name(&name)?;
349 remove_environment_name(&mut self.overrides, &mut self.override_names, &name);
350 self.override_names
351 .insert(EnvironmentNameKey::new(&name), name.clone());
352 self.overrides.insert(name, EnvironmentOverride::Remove);
353 Ok(self)
354 }
355
356 pub fn with_filter(
358 mut self,
359 pattern: impl Into<String>,
360 action: EnvironmentFilterAction,
361 ) -> Result<Self, CommandError> {
362 let pattern = EnvironmentPattern::new(pattern)?;
363 self.filters.remove(&pattern);
364 self.filters.insert(pattern, action);
365 Ok(self)
366 }
367
368 pub fn with_include_pattern(self, pattern: impl Into<String>) -> Result<Self, CommandError> {
370 self.with_filter(pattern, EnvironmentFilterAction::Include)
371 }
372
373 pub fn with_exclude_pattern(self, pattern: impl Into<String>) -> Result<Self, CommandError> {
375 self.with_filter(pattern, EnvironmentFilterAction::Exclude)
376 }
377}
378
379impl Default for EnvironmentSpec {
380 fn default() -> Self {
381 Self::inherit_core()
382 }
383}
384
385fn validate_name(name: &OsStr) -> Result<(), CommandError> {
386 if name.is_empty() {
387 return Err(CommandError::EmptyEnvironmentName);
388 }
389 if contains_nul(name) {
390 return Err(CommandError::EnvironmentNameContainsNul);
391 }
392 if name.to_string_lossy().contains('=') {
393 return Err(CommandError::EnvironmentNameContainsEquals);
394 }
395 Ok(())
396}
397
398fn collect_environment<I>(variables: I) -> Result<BTreeMap<OsString, OsString>, CommandError>
399where
400 I: IntoIterator<Item = (OsString, OsString)>,
401{
402 let mut collected = BTreeMap::new();
403 let mut names = HashMap::new();
404 for (name, value) in variables {
405 validate_name(&name)?;
406 if contains_nul(&value) {
407 return Err(CommandError::EnvironmentValueContainsNul);
408 }
409 remove_environment_name(&mut collected, &mut names, &name);
410 names.insert(EnvironmentNameKey::new(&name), name.clone());
411 collected.insert(name, value);
412 }
413 Ok(collected)
414}
415
416fn remove_environment_name<V>(
417 values: &mut BTreeMap<OsString, V>,
418 names: &mut HashMap<EnvironmentNameKey, OsString>,
419 name: &OsStr,
420) {
421 if let Some(existing) = names.remove(&EnvironmentNameKey::new(name)) {
422 values.remove(&existing);
423 }
424}
425
426fn environment_name_identity(name: &OsStr) -> EnvironmentNameIdentity {
427 if let Some(name) = name.to_str() {
428 return EnvironmentNameIdentity::Folded(case_folded_text(name));
429 }
430 #[cfg(unix)]
431 {
432 EnvironmentNameIdentity::NativeBytes(name.as_bytes().to_vec())
433 }
434 #[cfg(windows)]
435 {
436 EnvironmentNameIdentity::NativeWide(name.encode_wide().collect())
437 }
438}
439
440fn case_folded_text(value: &str) -> CaseFoldedText {
441 CaseFoldedText(
442 value
443 .chars()
444 .map(|character| character.to_lowercase().collect())
445 .collect(),
446 )
447}
448
449fn base_is_at_most(supplied: EnvironmentBase, required: EnvironmentBase) -> bool {
450 match required {
451 EnvironmentBase::None => supplied == EnvironmentBase::None,
452 EnvironmentBase::Core => supplied != EnvironmentBase::All,
453 EnvironmentBase::All => true,
454 }
455}