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