Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{Error, RawElement, Token, UnexpectedToken, XmlValueParser};
|
||||
use crate::schema::{Attribute, AttributeValue, Element, NamedElement, Namespace};
|
||||
use quick_xml::{
|
||||
NsReader, XmlVersion,
|
||||
events::{Event, attributes::AttrError},
|
||||
name::ResolveResult,
|
||||
};
|
||||
|
||||
pub struct Tokenizer<'x> {
|
||||
xml: NsReader<&'x [u8]>,
|
||||
last_is_end: bool,
|
||||
}
|
||||
|
||||
impl<'x> Tokenizer<'x> {
|
||||
pub fn new(input: &'x [u8]) -> Self {
|
||||
let mut xml = NsReader::from_reader(input);
|
||||
xml.config_mut();
|
||||
Self {
|
||||
xml,
|
||||
last_is_end: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token(&'_ mut self) -> super::Result<Token<'_>> {
|
||||
loop {
|
||||
if self.last_is_end {
|
||||
self.last_is_end = false;
|
||||
return Ok(Token::ElementEnd);
|
||||
}
|
||||
|
||||
let (resolve_result, event) = self.xml.read_resolved_event()?;
|
||||
let tag = match event {
|
||||
Event::Start(tag) => tag,
|
||||
Event::Empty(tag) => {
|
||||
self.last_is_end = true;
|
||||
tag
|
||||
}
|
||||
Event::End(_) => {
|
||||
return Ok(Token::ElementEnd);
|
||||
}
|
||||
Event::Text(text) if text.iter().any(|ch| !ch.is_ascii_whitespace()) => {
|
||||
return text
|
||||
.xml_content(XmlVersion::Implicit1_0)
|
||||
.map(Token::Text)
|
||||
.map_err(|err| Error::Xml(Box::new(err.into())));
|
||||
}
|
||||
Event::GeneralRef(entity) => {
|
||||
let entity_ref: &[u8] = entity.as_ref();
|
||||
hashify::fnc_map!(entity_ref,
|
||||
b"lt" => { return Ok(Token::Text("<".into())); },
|
||||
b"gt" => { return Ok(Token::Text(">".into())); },
|
||||
b"amp" => { return Ok(Token::Text("&".into())); },
|
||||
b"apos" => { return Ok(Token::Text("'".into())); },
|
||||
b"quot" => { return Ok(Token::Text("\"".into())); },
|
||||
_ => {
|
||||
if let Ok(Some(gr)) = entity.resolve_char_ref() {
|
||||
return Ok(Token::Text(gr.to_string().into()));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return entity
|
||||
.xml_content(XmlVersion::Implicit1_0)
|
||||
.map(Token::Text)
|
||||
.map_err(|err| Error::Xml(Box::new(err.into())));
|
||||
}
|
||||
Event::CData(bytes) => return Ok(Token::Bytes(bytes.into_inner())),
|
||||
Event::Eof => return Ok(Token::Eof),
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Parse element
|
||||
let name = tag.name();
|
||||
match resolve_result {
|
||||
ResolveResult::Bound(raw_ns) if !raw_ns.as_ref().is_empty() => {
|
||||
if let (Some(ns), Some(element)) = (
|
||||
Namespace::try_parse(raw_ns.as_ref()),
|
||||
Element::try_parse(name.local_name().as_ref()).copied(),
|
||||
) {
|
||||
return Ok(Token::ElementStart {
|
||||
name: NamedElement { ns, element },
|
||||
raw: RawElement::new(tag)
|
||||
.with_namespace_static(ns.namespace().as_bytes()),
|
||||
});
|
||||
} else {
|
||||
return Ok(Token::UnknownElement(
|
||||
RawElement::new(tag).with_namespace(raw_ns),
|
||||
));
|
||||
}
|
||||
}
|
||||
ResolveResult::Unknown(p) => {
|
||||
return Err(Error::Xml(Box::new(quick_xml::Error::Namespace(
|
||||
quick_xml::name::NamespaceError::UnknownPrefix(p),
|
||||
))));
|
||||
}
|
||||
_ => {
|
||||
return Ok(Token::UnknownElement(RawElement::new(tag)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_named_element(&mut self) -> super::Result<NamedElement> {
|
||||
match self.token()? {
|
||||
Token::ElementStart { name, .. } => Ok(name),
|
||||
found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
|
||||
expected: None,
|
||||
found: found.into_owned(),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_named_element(&mut self, expected: NamedElement) -> super::Result<()> {
|
||||
match self.token()? {
|
||||
Token::ElementStart { name, .. } if name == expected => Ok(()),
|
||||
found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
|
||||
expected: Token::ElementStart {
|
||||
name: expected,
|
||||
raw: RawElement::default(),
|
||||
}
|
||||
.into(),
|
||||
found: found.into_owned(),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_named_element_or_eof(&mut self, expected: NamedElement) -> super::Result<bool> {
|
||||
match self.token()? {
|
||||
Token::ElementStart { name, .. } if name == expected => Ok(true),
|
||||
Token::Eof => Ok(false),
|
||||
found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
|
||||
expected: Token::ElementStart {
|
||||
name: expected,
|
||||
raw: RawElement::default(),
|
||||
}
|
||||
.into(),
|
||||
found: found.into_owned(),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_element_end(&mut self) -> super::Result<()> {
|
||||
match self.token()? {
|
||||
Token::ElementEnd => Ok(()),
|
||||
found => Err(Error::UnexpectedToken(Box::new(UnexpectedToken {
|
||||
expected: Token::ElementEnd.into(),
|
||||
found: found.into_owned(),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seek_element_end(&mut self) -> super::Result<()> {
|
||||
let mut depth = 1;
|
||||
loop {
|
||||
match self.token()? {
|
||||
Token::ElementStart { .. } | Token::UnknownElement(_) => depth += 1,
|
||||
Token::ElementEnd => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Token::Eof => return Err(Token::Eof.into_unexpected()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_string_value(&mut self) -> super::Result<Option<String>> {
|
||||
let mut depth = 1;
|
||||
let mut value: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match self.token()? {
|
||||
Token::ElementStart { .. } | Token::UnknownElement(_) => depth += 1,
|
||||
Token::ElementEnd => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Token::Text(text) => {
|
||||
if let Some(ref mut v) = value {
|
||||
v.push_str(&text);
|
||||
} else {
|
||||
value = Some(text.into_owned());
|
||||
}
|
||||
}
|
||||
Token::Bytes(bytes) => {
|
||||
if let Some(ref mut v) = value {
|
||||
v.push_str(&String::from_utf8_lossy(&bytes));
|
||||
} else {
|
||||
value = Some(String::from_utf8_lossy(&bytes).into_owned());
|
||||
}
|
||||
}
|
||||
Token::Eof => return Err(Token::Eof.into_unexpected()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn parse_value<T: XmlValueParser>(&mut self) -> super::Result<Option<Result<T, String>>> {
|
||||
let mut depth = 1;
|
||||
let mut result: Option<Result<T, String>> = None;
|
||||
|
||||
loop {
|
||||
match self.token()? {
|
||||
Token::ElementStart { .. } | Token::UnknownElement(_) => depth += 1,
|
||||
Token::ElementEnd => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Token::Text(text) => {
|
||||
if let Some(value) = T::parse_str(&text) {
|
||||
result = Some(Ok(value));
|
||||
} else {
|
||||
result = Some(Err(text.into_owned()));
|
||||
}
|
||||
}
|
||||
Token::Bytes(bytes) => {
|
||||
if let Some(value) = T::parse_bytes(&bytes) {
|
||||
result = Some(Ok(value));
|
||||
} else {
|
||||
result = Some(Err(String::from_utf8_lossy(&bytes).into_owned()));
|
||||
}
|
||||
}
|
||||
Token::Eof => return Err(Token::Eof.into_unexpected()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn collect_elements<T>(&mut self) -> super::Result<Vec<T>>
|
||||
where
|
||||
T: TryFrom<NamedElement>,
|
||||
{
|
||||
let mut elements = Vec::with_capacity(2);
|
||||
let mut depth = 1;
|
||||
|
||||
loop {
|
||||
match self.token()? {
|
||||
Token::ElementStart { name, .. } => {
|
||||
if depth == 1
|
||||
&& let Ok(element) = T::try_from(name)
|
||||
{
|
||||
elements.push(element);
|
||||
}
|
||||
|
||||
depth += 1;
|
||||
}
|
||||
Token::UnknownElement(_) => {
|
||||
depth += 1;
|
||||
}
|
||||
Token::ElementEnd => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Token::Eof => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawElement<'_> {
|
||||
pub fn attributes<T: AttributeValue>(
|
||||
&self,
|
||||
) -> impl Iterator<Item = super::Result<Attribute<T>>> + '_ {
|
||||
self.element.attributes().filter_map(|attr| match attr {
|
||||
Ok(attr) => match attr.normalized_value(XmlVersion::Implicit1_0) {
|
||||
Ok(value) => Attribute::from_param(attr.key.as_ref(), value).map(Ok),
|
||||
Err(err) => Some(Err(err.into())),
|
||||
},
|
||||
Err(err) => Some(Err(err.into())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quick_xml::Error> for Error {
|
||||
fn from(err: quick_xml::Error) -> Self {
|
||||
Error::Xml(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttrError> for Error {
|
||||
fn from(err: AttrError) -> Self {
|
||||
Error::Xml(Box::new(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::schema::{Collation, MatchType};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TestToken<'x> {
|
||||
ElementStart(NamedElement),
|
||||
ElementEnd,
|
||||
Attribute(Attribute<String>),
|
||||
Bytes(Cow<'x, [u8]>),
|
||||
Text(Cow<'x, str>),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenizer() {
|
||||
for (input, expected) in [
|
||||
(
|
||||
r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:calendar-query xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:prop>
|
||||
<D:getetag/>
|
||||
<C:calendar-data/>
|
||||
</D:prop>
|
||||
<C:filter>
|
||||
<C:comp-filter name="VCALENDAR"/>
|
||||
</C:filter>
|
||||
</C:calendar-query>"#,
|
||||
vec![
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CalDav,
|
||||
element: Element::CalendarQuery,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::Dav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::Dav,
|
||||
element: Element::Getetag,
|
||||
}),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CalDav,
|
||||
element: Element::CalendarData,
|
||||
}),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CalDav,
|
||||
element: Element::Filter,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CalDav,
|
||||
element: Element::CompFilter,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("VCALENDAR".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
],
|
||||
),
|
||||
(
|
||||
r#" <?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:addressbook-query xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||
<D:prop>
|
||||
<D:getetag/>
|
||||
<C:address-data>
|
||||
<C:prop name="VERSION"/>
|
||||
<C:prop name="UID"/>
|
||||
<C:prop name="NICKNAME"/>
|
||||
<C:prop name="EMAIL"/>
|
||||
<C:prop name="FN"/>
|
||||
</C:address-data>
|
||||
</D:prop>
|
||||
<C:filter>
|
||||
<C:prop-filter name="NICKNAME">
|
||||
<C:text-match collation="i;unicode-casemap"
|
||||
match-type="equals"
|
||||
>me</C:text-match>
|
||||
</C:prop-filter>
|
||||
</C:filter>
|
||||
</C:addressbook-query>"#,
|
||||
vec![
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::AddressbookQuery,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::Dav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::Dav,
|
||||
element: Element::Getetag,
|
||||
}),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::AddressData,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("VERSION".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("UID".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("NICKNAME".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("EMAIL".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Prop,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("FN".to_string())),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::Filter,
|
||||
}),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::PropFilter,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Name("NICKNAME".to_string())),
|
||||
TestToken::ElementStart(NamedElement {
|
||||
ns: Namespace::CardDav,
|
||||
element: Element::TextMatch,
|
||||
}),
|
||||
TestToken::Attribute(Attribute::Collation(Collation::UnicodeCasemap)),
|
||||
TestToken::Attribute(Attribute::MatchType(MatchType::Equals)),
|
||||
TestToken::Text("me".into()),
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
TestToken::ElementEnd,
|
||||
],
|
||||
),
|
||||
] {
|
||||
let mut tokenizer = Tokenizer::new(input.as_bytes());
|
||||
let mut result = vec![];
|
||||
|
||||
loop {
|
||||
match tokenizer.token() {
|
||||
Ok(token) => match token {
|
||||
Token::ElementStart { name, raw } => {
|
||||
result.push(TestToken::ElementStart(name));
|
||||
for attr in raw.attributes::<String>() {
|
||||
result.push(TestToken::Attribute(attr.unwrap()));
|
||||
}
|
||||
}
|
||||
Token::ElementEnd => {
|
||||
result.push(TestToken::ElementEnd);
|
||||
}
|
||||
Token::Bytes(cow) => {
|
||||
result.push(TestToken::Bytes(cow.into_owned().into()));
|
||||
}
|
||||
Token::Text(cow) => {
|
||||
result.push(TestToken::Text(cow.into_owned().into()));
|
||||
}
|
||||
Token::UnknownElement(_) => {
|
||||
//result.push(TestToken::UnknownElement(unknown_element));
|
||||
}
|
||||
Token::Eof => break,
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Error: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user