#![allow(rustdoc::bare_urls)]
use std::collections::HashMap;
use crate::item::{Node, NodeType};
use crate::parser::xpath::parse;
use crate::transform::Transform;
use crate::transform::callable::{ActualParameters, FormalParameters};
use crate::transform::context::{ContextBuilder, StaticContextBuilder};
use crate::xdmerror::{Error, ErrorKind};
use crate::xslt::{ATTRNAME, ATTRSELECT, XSLPARAM, to_transform};
use qualname::{NamespaceUri, NcName, QName};
#[derive(Clone, Debug, PartialEq)]
pub enum SecurityResult {
Permitted(Option<String>),
NotPermitted,
}
#[derive(Clone, Debug)]
pub struct SecurityPolicies<N: Node> {
policies: HashMap<QName, Policy<N>>,
in_force: Option<QName>,
}
impl<N: Node> SecurityPolicies<N> {
pub fn new() -> Self {
Self {
policies: HashMap::new(),
in_force: None,
}
}
pub fn push(&mut self, policy: Policy<N>) {
self.policies.insert(policy.name.clone(), policy);
}
pub fn find(&self, name: QName) -> Option<&Policy<N>> {
self.policies.get(&name)
}
pub fn activate(&mut self, name: &QName) -> Option<&Policy<N>> {
self.policies.get_key_value(name).map(|(_k, v)| {
self.in_force = Some(name.clone());
v
})
}
pub fn get(&self, f: &QName, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
if self.in_force.is_none() {
return Ok(SecurityResult::NotPermitted);
}
if let Some(p) = self.policies.get(&self.in_force.as_ref().unwrap()) {
p.get(f, a)
} else {
Ok(SecurityResult::NotPermitted)
}
}
}
#[derive(Clone, Debug)]
pub struct Policy<N: Node> {
name: QName,
features: HashMap<QName, Feature<N>>,
}
impl<N: Node> Policy<N> {
pub fn new(name: QName) -> Self {
Self {
name,
features: HashMap::new(),
}
}
pub fn name(&self) -> QName {
self.name.clone()
}
pub fn add(&mut self, name: QName, f: Feature<N>) {
self.features.insert(name, f);
}
pub fn feature(&self, name: &QName) -> Option<&Feature<N>> {
self.features.get(name)
}
pub fn get(&self, name: &QName, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
self.features
.get(name)
.map_or_else(|| Ok(SecurityResult::NotPermitted), |f| f.get(a))
}
}
pub trait SecurityPolicy {
fn to_policy(&self) -> Result<Policy<Self>, Error>
where
Self: Node,
{
let secnsuri =
NamespaceUri::try_from("http://gitlab.gnome.org/World/Rust/markup-rs/Security")
.unwrap();
if let Some(top) = self.first_child() {
if !top.name().is_some_and(|qn| {
qn == QName::new_from_parts(
NcName::try_from("policy").unwrap(),
Some(secnsuri.clone()),
)
}) {
return Err(Error::new(
ErrorKind::TypeError,
"not a security policy document",
));
}
let name = top
.get_attribute(&QName::from_local_name(NcName::try_from("name").unwrap()))
.to_string();
if name != "" {
let mut policy = Policy::new(top.to_qname(name)?);
let fname = QName::new_from_parts(
NcName::try_from("feature").unwrap(),
Some(secnsuri.clone()),
);
top.child_iter()
.filter(|c| c.name().is_some_and(|n| n == fname))
.try_for_each(|f| {
let feat_name = f
.get_attribute(&QName::from_local_name(
NcName::try_from("name").unwrap(),
))
.to_string();
if feat_name != "" {
let mut body: Vec<Transform<Self>> = vec![];
let attr_sets: HashMap<QName, Vec<Transform<Self>>> = HashMap::new();
f.descend_iter()
.filter(|ws| {
ws.node_type() == NodeType::Text
&& ws.value().to_string().trim().is_empty()
})
.for_each(|mut ws| {
ws.pop().expect("unable to remove whitespace node")
});
let mut params: Vec<(QName, Option<Transform<Self>>)> = Vec::new();
f.child_iter()
.filter(|d| d.name().is_some_and(|e| e == *XSLPARAM))
.try_for_each(|d| {
let p_name = d.get_attribute(&ATTRNAME);
if p_name.to_string().is_empty() {
Err(Error::new(
ErrorKind::StaticAbsent,
"name attribute is missing",
))
} else {
let sel = d.get_attribute(&ATTRSELECT);
if sel.to_string().is_empty() {
let mut body = vec![];
d.child_iter().try_for_each(|e| {
body.push(to_transform(e, &attr_sets)?);
Ok(())
})?;
params.push((
QName::from_local_name(
NcName::try_from(p_name.to_string().as_str())
.map_err(|_| {
Error::new(
ErrorKind::ParseError,
"not a QName",
)
})?,
),
Some(Transform::SequenceItems(body)),
));
Ok(())
} else {
params.push((
QName::from_local_name(
NcName::try_from(p_name.to_string().as_str())
.map_err(|_| {
Error::new(
ErrorKind::ParseError,
"not a QName",
)
})?,
),
Some(parse::<Self>(
&sel.to_string(),
Some(d.clone()),
None,
)?),
));
Ok(())
}
}
})?;
f.child_iter()
.filter(|d| d.name().is_some_and(|e| e != *XSLPARAM))
.try_for_each(|d| {
body.push(to_transform(d, &attr_sets)?);
Ok::<(), Error>(())
})?;
if body.len() == 0 {
return Err(Error::new(
ErrorKind::TypeError,
format!("template for feature {} must not be empty", feat_name),
));
}
if body.len() == 1 {
policy.add(
top.to_qname(feat_name)?,
Feature::new(body.remove(0), FormalParameters::Named(params)),
)
} else {
policy.add(
top.to_qname(feat_name)?,
Feature::new(
Transform::SequenceItems(body),
FormalParameters::Named(params),
),
)
}
} else {
return Err(Error::new(
ErrorKind::DynamicAbsent,
"feature must have a name",
));
}
Ok(())
})?;
Ok(policy)
} else {
Err(Error::new(
ErrorKind::DynamicAbsent,
"name attribute is required",
))
}
} else {
Err(Error::new(ErrorKind::DynamicAbsent, "empty document"))
}
}
}
#[derive(Clone, Debug)]
pub struct Feature<N: Node> {
t: Transform<N>,
parameters: FormalParameters<N>,
}
impl<N: Node> Feature<N> {
pub fn new(t: Transform<N>, parameters: FormalParameters<N>) -> Self {
Feature { t, parameters }
}
pub fn get(&self, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
let mut stctxt = StaticContextBuilder::new()
.message(|_| Ok(()))
.parser(|_| {
Err(Error::new(
ErrorKind::StaticBadFunction,
"external resources are not allowed",
))
})
.fetcher(|_: &_| {
Err(Error::new(
ErrorKind::StaticBadFunction,
"external resources are not allowed",
))
})
.build();
let rd = N::new_document();
let mut ctxt = ContextBuilder::new().result_document(rd).build();
if let ActualParameters::Named(ap) = a {
ap.iter().try_for_each(|(an, av)| {
ctxt.var_push(an.to_string(), ctxt.dispatch(&mut stctxt, av)?);
Ok(())
})?;
if let FormalParameters::Named(fp) = &self.parameters {
fp.iter().try_for_each(|frm| {
if ctxt.var_value(frm.0.to_string()).is_none() {
if let Some(vv) = &frm.1 {
ctxt.var_push(frm.0.to_string(), ctxt.dispatch(&mut stctxt, &vv)?);
} else {
ctxt.var_push(frm.0.to_string(), vec![]);
}
}
Ok(())
})?;
}
}
let np = QName::new_from_parts(
NcName::try_from("not-permitted").unwrap(),
Some(
NamespaceUri::try_from("http://gitlab.gnome.org/World/Rust/markup-rs/Security")
.unwrap(),
),
);
let p = QName::new_from_parts(
NcName::try_from("permitted").unwrap(),
Some(
NamespaceUri::try_from("http://gitlab.gnome.org/World/Rust/markup-rs/Security")
.unwrap(),
),
);
let r = ctxt.dispatch(&mut stctxt, &self.t)?;
if r.len() == 1 {
if r[0].is_element_node() {
if r[0].name().unwrap() == np {
Ok(SecurityResult::NotPermitted)
} else if r[0].name().unwrap() == p {
let content = r[0].to_string();
if content.is_empty() {
Ok(SecurityResult::Permitted(None))
} else {
Ok(SecurityResult::Permitted(Some(content)))
}
} else {
Err(Error::new(
ErrorKind::TypeError,
"result must be a permitted or not-permitted element",
))
}
} else {
Err(Error::new(
ErrorKind::DynamicAbsent,
"result must be an element",
))
}
} else {
Err(Error::new(
ErrorKind::DynamicAbsent,
"result must be a single element",
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::item::Item;
use crate::trees::smite::RNode;
use crate::value::Value;
use std::rc::Rc;
#[test]
fn feature_get_np() {
let f = Feature::new(
Transform::LiteralElement(
QName::new_from_parts(
NcName::try_from("not-permitted").unwrap(),
Some(
NamespaceUri::try_from(
"http://gitlab.gnome.org/World/Rust/markup-rs/Security",
)
.unwrap(),
),
),
Box::new(Transform::<RNode>::Empty),
),
FormalParameters::Named(vec![]),
);
assert_eq!(
f.get(ActualParameters::Named(vec![]))
.expect("unable to determine status of security feature"),
SecurityResult::NotPermitted
)
}
#[test]
fn feature_get_unlimited() {
let f = Feature::new(
Transform::LiteralElement(
QName::new_from_parts(
NcName::try_from("permitted").unwrap(),
Some(
NamespaceUri::try_from(
"http://gitlab.gnome.org/World/Rust/markup-rs/Security",
)
.unwrap(),
),
),
Box::new(Transform::<RNode>::Empty),
),
FormalParameters::Named(vec![]),
);
assert_eq!(
f.get(ActualParameters::Named(vec![]))
.expect("unable to determine status of security feature"),
SecurityResult::Permitted(None)
)
}
#[test]
fn feature_get_limited() {
let f = Feature::new(
Transform::LiteralElement(
QName::new_from_parts(
NcName::try_from("permitted").unwrap(),
Some(
NamespaceUri::try_from(
"http://gitlab.gnome.org/World/Rust/markup-rs/Security",
)
.unwrap(),
),
),
Box::new(Transform::Literal(Item::<RNode>::Value(Rc::new(
Value::from(1234),
)))),
),
FormalParameters::Named(vec![]),
);
assert_eq!(
f.get(ActualParameters::Named(vec![]))
.expect("unable to determine status of security feature"),
SecurityResult::Permitted(Some(String::from("1234")))
)
}
}