use std::path::Path;
use std::process::Child;
pub fn is_safe_url(url: &str) -> bool {
(url.starts_with("https://") || url.starts_with("http://"))
&& !url.chars().any(|c| c.is_control() || c.is_whitespace() || c == '"')
}
pub fn open_url(url: &str, background: bool) -> Result<(), String> {
if !is_safe_url(url) {
return Err("solo se abren direcciones http(s) sin caracteres especiales".into());
}
super::sys::open_url(url, background)
}
pub fn reveal(path: &Path) -> Result<(), String> {
ensure_exists(path)?;
super::sys::reveal(path)
}
pub fn trash(path: &Path) -> Result<(), String> {
ensure_exists(path)?;
super::sys::trash(path).map_err(|error| format!("no se pudo mandar a la papelera: {error}"))
}
fn ensure_exists(path: &Path) -> Result<(), String> {
if path.exists() { Ok(()) } else { Err(format!("«{}» ya no existe", path.display())) }
}
#[cfg_attr(any(windows, target_os = "ios", target_os = "android"), allow(dead_code))]
pub(super) fn reap(mut child: Child) -> Result<(), String> {
std::thread::Builder::new()
.name("aura-reap".into())
.stack_size(64 * 1024)
.spawn(move || drop(child.wait()))
.map(drop)
.map_err(|error| format!("no se pudo vigilar el proceso: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn acepta_urls_http_normales_de_oauth() {
assert!(is_safe_url("https://auth.example.com/oauth/authorize?client_id=a&state=b"));
}
#[test]
fn rechaza_esquemas_distintos_y_metacaracteres() {
let bad = ["file:///etc/passwd", "-a Calculator", "javascript:alert(1)", "https://a.com/ b", "ftp://a.com"];
for url in bad.into_iter().chain(["https://a.com/\nx", "https://a.com/\"x"]) {
assert!(!is_safe_url(url), "{url}");
assert!(open_url(url, false).is_err());
}
}
#[test]
fn lo_que_no_existe_no_lanza_nada() {
let missing = std::env::temp_dir().join("aura-no-existe-xyz");
assert!(reveal(&missing).unwrap_err().contains("ya no existe"));
assert!(trash(&missing).unwrap_err().contains("ya no existe"));
}
}