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