use html5ever::{LocalName, Namespace, Prefix, ns};
use script_bindings::error::{Error, Fallible};
use script_bindings::str::DOMString;
const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
const XMLNS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns/";
fn is_valid_namespace_prefix(p: &str) -> bool {
if p.is_empty() {
return false;
}
!p.chars()
.any(|c| c.is_ascii_whitespace() || matches!(c, '\u{0000}' | '\u{002F}' | '\u{003E}'))
}
pub(crate) fn is_valid_attribute_local_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
!name.chars().any(|c| {
c.is_ascii_whitespace() || matches!(c, '\u{0000}' | '\u{002F}' | '\u{003D}' | '\u{003E}')
})
}
pub(crate) fn is_valid_element_local_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut iter = name.chars();
let c0 = iter.next().unwrap();
if c0.is_ascii_alphabetic() {
for c in iter {
if c.is_ascii_whitespace() || matches!(c, '\u{0000}' | '\u{002F}' | '\u{003E}') {
return false;
}
}
true
}
else if matches!(c0, '\u{003A}' | '\u{005F}' | '\u{0080}'..='\u{10FFF}') {
for c in iter {
if !c.is_ascii_alphanumeric() &&
!matches!(
c,
'\u{002D}' | '\u{002E}' | '\u{003A}' | '\u{005F}' | '\u{0080}'..='\u{10FFF}'
)
{
return false;
}
}
true
} else {
false
}
}
pub(crate) fn is_valid_doctype_name(name: &DOMString) -> bool {
!name
.str()
.chars()
.any(|c| c.is_ascii_whitespace() || matches!(c, '\u{0000}' | '\u{003E}'))
}
pub(crate) fn is_custom_data_attribute(name: &str, namespace: Option<&str>) -> bool {
namespace.is_none() &&
name.strip_prefix("data-")
.is_some_and(|substring| !substring.is_empty()) &&
is_valid_attribute_local_name(name) &&
name.chars()
.all(|code_point| !code_point.is_ascii_uppercase())
}
pub(crate) fn namespace_from_domstring(url: Option<DOMString>) -> Namespace {
match url {
None => ns!(),
Some(s) => Namespace::from(s),
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Context {
Attribute,
Element,
}
pub(crate) fn validate_and_extract(
namespace: Option<DOMString>,
qualified_name: &DOMString,
context: Context,
) -> Fallible<(Namespace, Option<Prefix>, LocalName)> {
let qualified_name = String::from(&*qualified_name.str());
let namespace = namespace_from_domstring(namespace);
let mut prefix = None;
let mut local_name = qualified_name.as_str();
if let Some((pre, name)) = qualified_name.split_once(':') {
prefix = Some(pre);
local_name = name;
if !is_valid_namespace_prefix(pre) {
debug!("Not a valid namespace prefix");
return Err(Error::InvalidCharacter(None));
}
}
if let Some(p) = prefix {
if !is_valid_namespace_prefix(p) {
debug!("Not a valid namespace prefix");
return Err(Error::InvalidCharacter(None));
}
}
match context {
Context::Attribute => {
if !is_valid_attribute_local_name(local_name) {
debug!("Not a valid attribute name");
return Err(Error::InvalidCharacter(None));
}
},
Context::Element => {
if !is_valid_element_local_name(local_name) {
debug!("Not a valid element name");
return Err(Error::InvalidCharacter(None));
}
},
}
match prefix {
Some(_) if namespace.is_empty() => Err(Error::Namespace(None)),
Some("xml") if *namespace != *XML_NAMESPACE => Err(Error::Namespace(None)),
p if (qualified_name == "xmlns" || p == Some("xmlns")) &&
*namespace != *XMLNS_NAMESPACE =>
{
Err(Error::Namespace(None))
},
Some(_) if qualified_name == "xmlns" && *namespace != *XMLNS_NAMESPACE => {
Err(Error::Namespace(None))
},
p if *namespace == *XMLNS_NAMESPACE &&
(qualified_name != "xmlns" && p != Some("xmlns")) =>
{
Err(Error::Namespace(None))
},
_ => Ok((
namespace,
prefix.map(Prefix::from),
LocalName::from(local_name),
)),
}
}