Mountain/IPC/WindServiceHandlers/Utilities/
PathExtraction.rs1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3use serde_json::Value;
10
11use super::{ApplicationRoot::get_static_application_root, UserdataDir::get_userdata_base_dir};
12use crate::dev_log;
13
14pub fn extract_path_from_arg(Arg:&Value) -> Result<String, String> {
21 if let Some(Path) = Arg.as_str() {
22 return Ok(normalize_uri_path(Path));
23 }
24
25 if let Some(Object) = Arg.as_object() {
26 if let Some(FsPath) = Object.get("fsPath").and_then(|V| V.as_str()) {
27 if !FsPath.is_empty() {
28 return Ok(FsPath.to_string());
29 }
30 }
31
32 if let Some(Path) = Object.get("path").and_then(|V| V.as_str()) {
33 if !Path.is_empty() {
34 return Ok(normalize_uri_path(Path));
35 }
36 }
37
38 if let Some(External) = Object.get("external").and_then(|V| V.as_str()) {
39 if External.starts_with("file://") {
40 let Stripped = External.trim_start_matches("file://");
41
42 return Ok(normalize_uri_path(Stripped));
43 }
44 }
45 }
46
47 Err("File path must be a string or URI object with path/fsPath field".to_string())
48}
49
50fn normalize_uri_path(Path:&str) -> String {
51 let Decoded = percent_decode(Path);
52
53 let Resolved = resolve_userdata_path(&Decoded);
54
55 let Resolved = resolve_static_application_path(&Resolved);
56
57 #[cfg(target_os = "windows")]
58 {
59 let Trimmed = if Resolved.len() >= 3 && Resolved.starts_with('/') && Resolved.as_bytes().get(2) == Some(&b':') {
60 Resolved[1..].to_string()
61 } else {
62 Resolved
63 };
64
65 Trimmed.replace('/', "\\")
66 }
67
68 #[cfg(not(target_os = "windows"))]
69 {
70 Resolved
71 }
72}
73
74fn resolve_userdata_path(Path:&str) -> String {
75 if !Path.starts_with("/User/") && Path != "/User" {
76 return Path.to_string();
77 }
78
79 let UserDataBase = get_userdata_base_dir();
80
81 let Resolved = format!("{}{}", UserDataBase, Path);
82
83 dev_log!("vfs", "resolve_userdata: {} -> {}", Path, Resolved);
84
85 Resolved
86}
87
88fn resolve_static_application_path(Path:&str) -> String {
96 let Normalized = if Path.starts_with("/Static/Application/") || Path == "/Static/Application" {
97 Path.to_string()
98 } else if Path.starts_with("Static/Application/") || Path == "Static/Application" {
99 format!("/{}", Path)
100 } else {
101 return Path.to_string();
102 };
103
104 if let Some(Root) = get_static_application_root() {
105 let Relative = Normalized.strip_prefix("/Static/Application").unwrap_or("");
106
107 let Resolved = format!("{}/Static/Application{}", Root, Relative);
108
109 dev_log!("vfs", "resolve_static: {} -> {}", Path, Resolved);
110
111 Resolved
112 } else {
113 Path.to_string()
114 }
115}
116
117pub fn percent_decode(Input:&str) -> String {
120 let mut Result = String::with_capacity(Input.len());
121
122 let Bytes = Input.as_bytes();
123
124 let mut I = 0;
125
126 while I < Bytes.len() {
127 if Bytes[I] == b'%' && I + 2 < Bytes.len() {
128 let High = hex_digit(Bytes[I + 1]);
129
130 let Low = hex_digit(Bytes[I + 2]);
131
132 if let (Some(H), Some(L)) = (High, Low) {
133 Result.push((H * 16 + L) as char);
134
135 I += 3;
136
137 continue;
138 }
139 }
140
141 Result.push(Bytes[I] as char);
142
143 I += 1;
144 }
145
146 Result
147}
148
149pub fn hex_digit(Byte:u8) -> Option<u8> {
150 match Byte {
151 b'0'..=b'9' => Some(Byte - b'0'),
152
153 b'a'..=b'f' => Some(Byte - b'a' + 10),
154
155 b'A'..=b'F' => Some(Byte - b'A' + 10),
156
157 _ => None,
158 }
159}