use std::path::{Path, PathBuf};
use kernel::{Address, AxCode, AxError};
use super::Found;
use super::miss::Floor;
use crate::catalog::{Catalog, Expansion};
use crate::tools::chosen_path::real_location;
pub(super) fn open_in_package(
catalog: &Catalog,
city_root: &Path,
asked: &str,
) -> Option<Result<Found, AxError>> {
let (name, inside) = asked.split_once('/')?;
let Some(Expansion::Skill {
package: Some(package),
..
}) = catalog.expand(name)
else {
return None;
};
let package = package.as_str();
Some(within(package, inside, asked).and_then(|target| {
let shelf = under(
&real_location(city_root, "read", asked)?.into_path(),
package,
);
let at = real_location(&under(city_root, target.as_str()), "read", asked)?;
stays_inside(&at, &shelf, asked)?;
Ok(Found::File {
at,
floor: Floor::Directory {
dir: shelf,
named: name.to_owned(),
},
})
}))
}
pub(super) fn open_document(
city_root: &Path,
shelved: &Address,
asked: &str,
) -> Result<Found, AxError> {
let spelled = under(
&real_location(city_root, "read", asked)?.into_path(),
shelved.as_str(),
);
let at = real_location(&under(city_root, shelved.as_str()), "read", asked)?;
if at.path() != spelled.as_path() {
return Err(AxError::failure(
AxCode::GateDenied,
"read",
format!("{asked} is shelved behind a link"),
)
.with_recovery(
"the User has to put the skill's document itself on the shelf, not a link to it",
));
}
Ok(Found::File {
at,
floor: Floor::Document,
})
}
fn stays_inside(at: &super::Located, shelf: &Path, asked: &str) -> Result<(), AxError> {
if at.path().starts_with(shelf) {
return Ok(());
}
Err(AxError::failure(
AxCode::GateDenied,
"read",
format!("{asked} leads out of the package through a link"),
)
.with_recovery("name a file that lies inside the package itself, not behind a link in it"))
}
fn under(root: &Path, addr: &str) -> PathBuf {
addr.split('/')
.fold(root.to_path_buf(), |path, segment| path.join(segment))
}
fn within(package: &str, inside: &str, asked: &str) -> Result<Address, AxError> {
let plain = inside
.split('/')
.all(|segment| !matches!(segment, "" | "." | "..") && !segment.contains(['\\', ':']));
if !plain {
return Err(AxError::failure(
AxCode::InvalidArgs,
"read",
format!("{asked} does not name a file inside the package"),
)
.with_recovery(
"write the path inside the package as plain segments joined by `/`, \
with no empty, `.` or `..` segment",
));
}
Address::parse(&format!("{package}/{inside}"))
}