1use std::fs;
7use std::path::{Path, PathBuf};
8
9use colored::Colorize;
10use mlua::{self, Lua, Value};
11use sequoia_openpgp::Cert;
12use sequoia_openpgp::parse::Parse;
13use zoi_core::utils;
14pub fn add_verify_hash(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
30 let verify_hash_fn =
31 lua.create_function(move |lua, args: mlua::MultiValue| {
32 let mut args_iter = args.into_iter();
33 let file_path = match args_iter.next().unwrap_or(Value::Nil) {
34 Value::String(s) => s.to_str()?.to_string(),
35 v => {
36 return Err(mlua::Error::RuntimeError(format!(
37 "verifyHash: first argument must be a string, got {}",
38 v.type_name()
39 )));
40 }
41 };
42 let hash_str = match args_iter.next().unwrap_or(Value::Nil) {
43 Value::String(s) => s.to_str()?.to_string(),
44 v => {
45 return Err(mlua::Error::RuntimeError(format!(
46 "verifyHash: second argument must be a string, got {}",
47 v.type_name()
48 )));
49 }
50 };
51
52 let parts: Vec<&str> = hash_str.splitn(2, '-').collect();
53 if parts.len() != 2 {
54 return Err(mlua::Error::RuntimeError(
55 "Invalid hash format. Expected 'algo-hash'".to_string()
56 ));
57 }
58 let algo = parts.first().copied().unwrap_or_default();
59 let expected_hash = parts.get(1).copied().unwrap_or_default();
60
61 let p = Path::new(&file_path);
62 let actual_path = if p.exists() {
63 p.to_path_buf()
64 } else if let Ok(build_dir) =
65 lua.globals().get::<String>("BUILD_DIR")
66 {
67 Path::new(&build_dir).join(p)
68 } else {
69 p.to_path_buf()
70 };
71
72 let Some(hash_algo) =
73 zoi_core::hash::HashAlgorithm::from_name(algo)
74 else {
75 return Err(mlua::Error::RuntimeError(format!(
76 "Unsupported hash algorithm: {algo}"
77 )));
78 };
79
80 let actual_hash = match zoi_core::hash::calculate_file_hash(
81 &actual_path,
82 hash_algo
83 ) {
84 Ok(h) => h,
85 Err(e) => {
86 return Err(mlua::Error::RuntimeError(format!(
87 "Failed to calculate hash: {e}"
88 )));
89 }
90 };
91
92 if actual_hash.eq_ignore_ascii_case(expected_hash) {
93 Ok(true)
94 } else {
95 if !quiet {
96 println!(
97 "\n{}: Hash mismatch for {}",
98 "Error".red().bold(),
99 file_path.cyan()
100 );
101 println!(" specified: {algo}-{expected_hash}");
102 println!(" got: {algo}-{actual_hash}");
103 }
104 Ok(false)
105 }
106 })?;
107 lua.globals().set("verifyHash", verify_hash_fn)?;
108 Ok(())
109}
110
111pub fn add_verify_signature(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
120 let verify_sig_fn =
121 lua.create_function(move |lua, args: mlua::MultiValue| {
122 let mut args_iter = args.into_iter();
123 let file_path = match args_iter.next().unwrap_or(Value::Nil) {
124 Value::String(s) => s.to_str()?.to_string(),
125 v => {
126 return Err(mlua::Error::RuntimeError(format!(
127 "verifySignature: first argument must be a string, \
128 got {}",
129 v.type_name()
130 )));
131 }
132 };
133 let sig_path = match args_iter.next().unwrap_or(Value::Nil) {
134 Value::String(s) => s.to_str()?.to_string(),
135 v => {
136 return Err(mlua::Error::RuntimeError(format!(
137 "verifySignature: second argument must be a string, \
138 got {}",
139 v.type_name()
140 )));
141 }
142 };
143 let key_source = match args_iter.next().unwrap_or(Value::Nil) {
144 Value::String(s) => s.to_str()?.to_string(),
145 v => {
146 return Err(mlua::Error::RuntimeError(format!(
147 "verifySignature: third argument must be a string, \
148 got {}",
149 v.type_name()
150 )));
151 }
152 };
153
154 let resolve_path = |p_str: &str| -> PathBuf {
155 let p = Path::new(p_str);
156 if p.exists() {
157 p.to_path_buf()
158 } else if let Ok(build_dir) =
159 lua.globals().get::<String>("BUILD_DIR")
160 {
161 Path::new(&build_dir).join(p)
162 } else {
163 p.to_path_buf()
164 }
165 };
166
167 let key_bytes: Vec<u8> = if key_source.starts_with("http") {
168 let client = utils::get_http_client()
169 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
170 #[allow(clippy::redundant_closure_for_method_calls)]
171 match client.get(&key_source).send().and_then(|r| r.bytes()) {
172 Ok(b) => b.to_vec(),
173 Err(e) => {
174 return Err(mlua::Error::RuntimeError(format!(
175 "Failed to download key: {e}"
176 )));
177 }
178 }
179 } else {
180 let resolved_key_path = resolve_path(&key_source);
181 if resolved_key_path.exists() {
182 match fs::read(&resolved_key_path) {
183 Ok(b) => b,
184 Err(e) => {
185 return Err(mlua::Error::RuntimeError(format!(
186 "Failed to read key file {}: {e}",
187 resolved_key_path.display()
188 )));
189 }
190 }
191 } else {
192 let pgp_dir = match zoi_core::pgp::get_pgp_dir() {
193 Ok(dir) => dir,
194 Err(e) => {
195 return Err(mlua::Error::RuntimeError(format!(
196 "Failed to get PGP dir: {e}"
197 )));
198 }
199 };
200 let key_path = pgp_dir.join(format!("{key_source}.asc"));
201 if !key_path.exists() {
202 return Err(mlua::Error::RuntimeError(format!(
203 "Key with name '{key_source}' not found (checked \
204 locally and at {}).",
205 resolved_key_path.display()
206 )));
207 }
208 match fs::read(&key_path) {
209 Ok(b) => b,
210 Err(e) => {
211 return Err(mlua::Error::RuntimeError(format!(
212 "Failed to read key file {}: {e}",
213 key_path.display()
214 )));
215 }
216 }
217 }
218 };
219
220 let cert = match Cert::from_bytes(&key_bytes) {
221 Ok(c) => c,
222 Err(e) => {
223 return Err(mlua::Error::RuntimeError(format!(
224 "Invalid PGP key: {e}"
225 )));
226 }
227 };
228
229 let final_file_path = resolve_path(&file_path);
230 let final_sig_path = resolve_path(&sig_path);
231
232 let result = zoi_core::pgp::verify_detached_signature(
233 &final_file_path,
234 &final_sig_path,
235 &cert
236 );
237
238 match result {
239 Ok(()) => Ok(true),
240 Err(e) => {
241 if !quiet {
242 eprintln!("Signature verification failed: {e}");
243 }
244 Ok(false)
245 }
246 }
247 })?;
248 lua.globals().set("verifySignature", verify_sig_fn)?;
249 Ok(())
250}
251
252pub fn add_add_pgp_key(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
261 let add_pgp_key_fn =
262 lua.create_function(move |lua, args: mlua::MultiValue| {
263 let mut args_iter = args.into_iter();
264 let source = match args_iter.next().unwrap_or(Value::Nil) {
265 Value::String(s) => s.to_str()?.to_string(),
266 v => {
267 return Err(mlua::Error::RuntimeError(format!(
268 "addPgpKey: first argument must be a string, got {}",
269 v.type_name()
270 )));
271 }
272 };
273 let name = match args_iter.next().unwrap_or(Value::Nil) {
274 Value::String(s) => s.to_str()?.to_string(),
275 v => {
276 return Err(mlua::Error::RuntimeError(format!(
277 "addPgpKey: second argument must be a string, got {}",
278 v.type_name()
279 )));
280 }
281 };
282
283 let result = if source.starts_with("http") {
284 zoi_core::pgp::add_key_from_url(&source, &name, quiet)
285 } else {
286 let p = Path::new(&source);
287 let actual_path = if p.exists() {
288 p.to_path_buf()
289 } else if let Ok(build_dir) =
290 lua.globals().get::<String>("BUILD_DIR")
291 {
292 Path::new(&build_dir).join(p)
293 } else {
294 p.to_path_buf()
295 };
296 zoi_core::pgp::add_key_from_path(
297 actual_path.to_str().unwrap_or(&source),
298 Some(&name),
299 quiet
300 )
301 };
302
303 if let Err(e) = result {
304 if !quiet {
305 eprintln!("Failed to add PGP key '{name}': {e}");
306 }
307 return Ok(false);
308 }
309 Ok(true)
310 })?;
311 lua.globals().set("addPgpKey", add_pgp_key_fn)?;
312 Ok(())
313}