Mountain/Environment/
Utility.rs

1//! # Environment Utility
2//!
3//! Contains shared helper functions used by the `MountainEnvironment` provider
4//! implementations for tasks like error mapping, path manipulation, and
5//! security checks.
6
7#![allow(non_snake_case, non_camel_case_types)]
8
9use std::{
10	ffi::OsStr,
11	path::Path,
12	sync::{MutexGuard, PoisonError},
13};
14
15use Common::Error::CommonError::CommonError;
16use log::{error, trace};
17use url::Url;
18
19use crate::ApplicationState::ApplicationState::ApplicationState;
20
21/// Maps a `PoisonError` from a failed `ApplicationState` Mutex lock into a
22/// structured `CommonError::StateLockPoisoned`.
23pub fn MapApplicationStateLockErrorToCommonError<T>(Error:PoisonError<MutexGuard<'_, T>>) -> CommonError {
24	let ErrorMessage = format!("[EnvironmentUtility] Failed to lock ApplicationState section: {}", Error);
25
26	error!("{}", ErrorMessage);
27
28	CommonError::StateLockPoisoned { Context:ErrorMessage }
29}
30
31/// A simple utility to detect a language identifier string from a file path's
32/// extension.
33pub fn DetectLanguageIdentifierFromFilePath(Path:&Path) -> String {
34	match Path.extension().and_then(OsStr::to_str) {
35		Some("js") | Some("mjs") | Some("cjs") => "javascript",
36
37		Some("ts") | Some("mts") | Some("cts") => "typescript",
38
39		Some("jsx") => "javascriptreact",
40
41		Some("tsx") => "typescriptreact",
42
43		Some("rs") => "rust",
44
45		Some("md") => "markdown",
46
47		Some("json") => "json",
48
49		Some("html") => "html",
50
51		Some("css") => "css",
52
53		_ => "plaintext",
54	}
55	.to_string()
56}
57
58/// A critical security helper that checks if a given filesystem path is
59/// allowed for access.
60///
61/// In this architecture, this means the path must be a descendant of one of the
62/// currently open and trusted workspace folders. This prevents extensions from
63/// performing arbitrary filesystem operations outside the user's intended
64/// scope.
65pub fn IsPathAllowedForAccess(ApplicationState:&ApplicationState, PathToCheck:&Path) -> Result<(), CommonError> {
66	trace!("[EnvironmentSecurity] Verifying path: {}", PathToCheck.display());
67
68	if !ApplicationState.IsTrusted.load(std::sync::atomic::Ordering::Relaxed) {
69		return Err(CommonError::FileSystemPermissionDenied {
70			Path:PathToCheck.to_path_buf(),
71
72			Reason:"Workspace is not trusted. File access is denied.".to_string(),
73		});
74	}
75
76	let FoldersGuard = ApplicationState
77		.WorkSpaceFolders
78		.lock()
79		.map_err(MapApplicationStateLockErrorToCommonError)?;
80
81	if FoldersGuard.is_empty() {
82		// Allow access if no folder is open, as operations are likely on user-chosen
83		// files. A stricter model could deny this.
84		return Ok(());
85	}
86
87	let IsAllowed = FoldersGuard.iter().any(|Folder| {
88		match Folder.URI.to_file_path() {
89			Ok(FolderPath) => PathToCheck.starts_with(FolderPath),
90
91			Err(_) => false,
92		}
93	});
94
95	if IsAllowed {
96		Ok(())
97	} else {
98		Err(CommonError::FileSystemPermissionDenied {
99			Path:PathToCheck.to_path_buf(),
100
101			Reason:"Path is outside of the registered workspace folders.".to_string(),
102		})
103	}
104}
105
106/// Helper to get a `Url` from a `serde_json::Value` which is expected to be a
107/// `UriComponents` DTO from VS Code.
108pub fn GetURLFromURIComponentsDTO(URIDTO:&serde_json::Value) -> Result<Url, CommonError> {
109	// VS Code's UriComponents DTO often serializes to an object with a path,
110
111	// scheme, etc., but also includes a pre-formatted 'external' string version.
112	let URIString = URIDTO.get("external").and_then(serde_json::Value::as_str).ok_or_else(|| {
113		CommonError::InvalidArgument {
114			ArgumentName:"URIDTO".to_string(),
115
116			Reason:"Missing 'external' string field in UriComponents DTO".to_string(),
117		}
118	})?;
119
120	Url::parse(URIString).map_err(|Error| {
121		CommonError::InvalidArgument {
122			ArgumentName:"URIDTO.external".to_string(),
123
124			Reason:format!("Failed to parse URI string '{}': {}", URIString, Error),
125		}
126	})
127}