mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-18 07:27:42 +02:00
549 lines
17 KiB
Rust
549 lines
17 KiB
Rust
//! Support for RPKI XML structures.
|
|
use std::{fs, io};
|
|
use std::fs::File;
|
|
use std::path::Path;
|
|
use base64;
|
|
use base64::DecodeError;
|
|
use bytes::Bytes;
|
|
use hex;
|
|
use hex::FromHexError;
|
|
use xmlrs::{reader, writer};
|
|
use xmlrs::{EmitterConfig, EventReader, EventWriter, ParserConfig};
|
|
use xmlrs::attribute::OwnedAttribute;
|
|
use xmlrs::reader::XmlEvent;
|
|
|
|
|
|
//------------ XmlReader -----------------------------------------------------
|
|
|
|
/// A convenience wrapper for RPKI XML parsing
|
|
///
|
|
/// This type only exposes things we need for the RPKI XML structures.
|
|
pub struct XmlReader<R: io::Read> {
|
|
/// The underlying xml-rs reader
|
|
reader: EventReader<R>,
|
|
|
|
/// Placeholder for an event so that 'peak' can be supported, as
|
|
/// well as temporarily caching a close event in case a list of
|
|
/// inner elements is processed.
|
|
cached_event: Option<XmlEvent>,
|
|
|
|
/// Name of the next start element, if any
|
|
next_start_name: Option<String>
|
|
}
|
|
|
|
|
|
/// Reader methods
|
|
impl <R: io::Read> XmlReader<R> {
|
|
|
|
/// Gets the next XmlEvent
|
|
///
|
|
/// Will take cached event if there is one
|
|
fn next(&mut self) -> Result<XmlEvent, XmlReaderErr> {
|
|
match self.cached_event.take() {
|
|
Some(e) => Ok(e),
|
|
None => Ok(self.reader.next()?)
|
|
}
|
|
}
|
|
|
|
/// Puts an XmlEvent back so that it can be retrieved by 'next'
|
|
fn cache(&mut self, e: XmlEvent) {
|
|
self.cached_event = Some(e);
|
|
}
|
|
}
|
|
|
|
|
|
/// Basic operations to parse the XML.
|
|
///
|
|
/// These methods are private because they are used by the higher level
|
|
/// closure based methods, defined below, that one should use to parse
|
|
/// XML safely.
|
|
impl <R: io::Read> XmlReader<R> {
|
|
/// Takes the next element and expects a start of document.
|
|
fn start_document(&mut self) -> Result<(), XmlReaderErr> {
|
|
match self.next() {
|
|
Ok(reader::XmlEvent::StartDocument {..}) => Ok(()),
|
|
_ => Err(XmlReaderErr::ExpectedStartDocument)
|
|
}
|
|
}
|
|
|
|
/// Takes the next element and expects a start element with the given name.
|
|
fn expect_element(&mut self) -> Result<(Tag, Attributes), XmlReaderErr> {
|
|
match self.next() {
|
|
Ok(reader::XmlEvent::StartElement { name, attributes, ..}) => {
|
|
Ok((Tag{name: name.local_name}, Attributes{attributes}))
|
|
},
|
|
_ => Err(XmlReaderErr::ExpectedStart)
|
|
}
|
|
}
|
|
|
|
/// Takes the next element and expects a close element with the given name.
|
|
fn expect_close(&mut self, tag: Tag) -> Result<(), XmlReaderErr> {
|
|
match self.next() {
|
|
Ok(reader::XmlEvent::EndElement { name, ..}) => {
|
|
if name.local_name == tag.name {
|
|
Ok(())
|
|
} else {
|
|
Err(XmlReaderErr::ExpectedClose(tag.name))
|
|
}
|
|
}
|
|
_ => Err(XmlReaderErr::ExpectedClose(tag.name))
|
|
}
|
|
}
|
|
|
|
/// Takes the next element and expects the end of document.
|
|
///
|
|
/// Returns Ok(true) if the element is the end of document, or
|
|
/// an error otherwise.
|
|
fn end_document(&mut self) -> Result<(), XmlReaderErr> {
|
|
match self.next() {
|
|
Ok(reader::XmlEvent::EndDocument) => Ok(()),
|
|
_ => Err(XmlReaderErr::ExpectedEnd)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Closure based parsing of XML.
|
|
///
|
|
/// This approach ensures that the consumer can only get opening tags, or
|
|
/// content (such as Characters), and process the enclosed content. In
|
|
/// particular it ensures that the consumer cannot accidentally get close
|
|
/// tags - so it forces that execution returns.
|
|
impl <R: io::Read> XmlReader<R> {
|
|
/// Decodes an XML structure
|
|
///
|
|
/// This method checks that the document starts, then passes a reader
|
|
/// instance to the provided closure, and will return the result from
|
|
/// that after checking that the XML document is fully processed.
|
|
pub fn decode<F, T, E>(source: R, op: F) -> Result<T, E>
|
|
where F: FnOnce(&mut Self) -> Result<T, E>,
|
|
E: From<XmlReaderErr> {
|
|
let mut config = ParserConfig::new();
|
|
config.trim_whitespace = true;
|
|
config.ignore_comments = true;
|
|
|
|
let mut xml = XmlReader{
|
|
reader: config.create_reader(source),
|
|
cached_event: None,
|
|
next_start_name: None
|
|
};
|
|
|
|
xml.start_document()?;
|
|
let res = op(&mut xml)?;
|
|
xml.end_document()?;
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Takes an element and process it in a closure
|
|
///
|
|
/// This method checks that the next element is indeed a Start Element,
|
|
/// and passes the Tag and Attributes and this reader to a closure. After
|
|
/// the closure completes it will verify that the next element is the
|
|
/// Close Element for this Tag, and returns the result from the closure.
|
|
pub fn take_element<F, T, E>(&mut self, op: F) -> Result<T, E>
|
|
where F: FnOnce(&Tag, Attributes, &mut Self) -> Result<T, E>,
|
|
E: From<XmlReaderErr> {
|
|
let (tag, attr) = self.expect_element()?;
|
|
let res = op(&tag, attr, self)?;
|
|
self.expect_close(tag)?;
|
|
Ok(res)
|
|
}
|
|
|
|
/// Takes a named element and process it in a closure
|
|
///
|
|
/// Checks that the element has the expected name and passed the closure
|
|
/// to the generic take_element method.
|
|
pub fn take_named_element<F, T, E>(
|
|
&mut self,
|
|
name: &str,
|
|
op: F
|
|
) -> Result<T, E>
|
|
where
|
|
F: FnOnce(Attributes, &mut Self) -> Result<T, E>,
|
|
E: From<XmlReaderErr>
|
|
{
|
|
self.take_element(|t, a, r| {
|
|
if t.name != name {
|
|
Err(XmlReaderErr::ExpectedNamedStart(name.to_string()).into())
|
|
}
|
|
else {
|
|
op(a, r)
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Takes the next element that is part of a list of elements under the
|
|
/// current element, and processes it using a closure. When the end of the
|
|
/// list is encountered, i.e. the next element is not a start element, then
|
|
/// the closure is not executed and Ok(None) is returned. The element is
|
|
/// put back on the cache for processing by the parent structure.
|
|
///
|
|
/// Note: This will break if we encounter a parent XML element that has
|
|
/// both a list of children XML elements *and* some (character) content.
|
|
/// However, this is not used by the RPKI XML structures. Also, provided
|
|
/// that a 'take_*' method with a closure was used for the parent element,
|
|
/// then we will get a clear error there (expect end element).
|
|
pub fn take_opt_element<F, T, E>(&mut self, op: F) -> Result<Option<T>, E>
|
|
where F: FnOnce(&Tag, Attributes, &mut Self) -> Result<Option<T>, E>,
|
|
E: From<XmlReaderErr> {
|
|
|
|
let n = self.next()?;
|
|
match n {
|
|
XmlEvent::StartElement { name, attributes, ..} => {
|
|
let tag = Tag{name: name.local_name};
|
|
let res = op(
|
|
&tag,
|
|
Attributes{attributes},
|
|
self
|
|
);
|
|
self.expect_close(tag)?;
|
|
res
|
|
},
|
|
_ => {
|
|
self.cache(n);
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Takes characters
|
|
pub fn take_chars(&mut self) -> Result<String, XmlReaderErr> {
|
|
match self.next() {
|
|
Ok(reader::XmlEvent::Characters(chars)) => {
|
|
Ok(chars)
|
|
}
|
|
_ => Err(XmlReaderErr::ExpectedCharacters)
|
|
}
|
|
}
|
|
|
|
/// Takes base64 encoded bytes from the next 'characters' event.
|
|
pub fn take_bytes_characters(&mut self) -> Result<Bytes, XmlReaderErr> {
|
|
let b64 = base64::decode_config(&self.take_chars()?, base64::MIME)?;
|
|
Ok(Bytes::from(b64))
|
|
}
|
|
|
|
pub fn take_empty(&mut self) -> Result<(), XmlReaderErr> {
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the name of the next start element or None if the next
|
|
/// element is not a start element. Also ensures that the next element
|
|
/// is kept in the cache for normal subsequent processing.
|
|
pub fn next_start_name(&mut self) -> Option<&str> {
|
|
match self.next() {
|
|
Err(_) => None,
|
|
Ok(e) => {
|
|
if let XmlEvent::StartElement { ref name, ..} = e {
|
|
// XXX not the most efficient.. but need a different
|
|
// underlying XML parser to get around ownership
|
|
// issues.
|
|
self.next_start_name = Some(name.local_name.clone())
|
|
} else {
|
|
self.next_start_name = None;
|
|
}
|
|
self.cache(e);
|
|
self.next_start_name.as_ref().map(|s| s.as_ref())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl XmlReader<fs::File> {
|
|
|
|
/// Opens a file and decodes it as an XML file.
|
|
pub fn open<P, F, T, E>(path: P, op: F) -> Result<T, E>
|
|
where F: FnOnce(&mut Self) -> Result<T, E>,
|
|
P: AsRef<Path>,
|
|
E: From<XmlReaderErr> + From<io::Error> {
|
|
Self::decode(fs::File::open(path)?, op)
|
|
}
|
|
}
|
|
|
|
//------------ XmlReaderErr --------------------------------------------------
|
|
|
|
#[derive(Debug, Display)]
|
|
pub enum XmlReaderErr {
|
|
#[display(fmt = "Expected Start of Document")]
|
|
ExpectedStartDocument,
|
|
|
|
#[display(fmt = "Expected Start Element")]
|
|
ExpectedStart,
|
|
|
|
#[display(fmt = "Expected Start Element with name: {}", _0)]
|
|
ExpectedNamedStart(String),
|
|
|
|
#[display(fmt = "Expected Characters Element")]
|
|
ExpectedCharacters,
|
|
|
|
#[display(fmt = "Expected Close Element with name: {}", _0)]
|
|
ExpectedClose(String),
|
|
|
|
#[display(fmt = "Expected End of Document")]
|
|
ExpectedEnd,
|
|
|
|
#[display(fmt = "Error reading file: {}", _0)]
|
|
IoError(io::Error),
|
|
|
|
#[display(fmt = "Attributes Error: {}", _0)]
|
|
AttributesError(AttributesError),
|
|
|
|
#[display(fmt = "XML Reader Error: {}", _0)]
|
|
ReaderError(reader::Error),
|
|
|
|
#[display(fmt = "Base64 decoding issue: {}", _0)]
|
|
Base64Error(DecodeError)
|
|
}
|
|
|
|
impl From<io::Error> for XmlReaderErr {
|
|
fn from(e: io::Error) -> XmlReaderErr{
|
|
XmlReaderErr::IoError(e)
|
|
}
|
|
}
|
|
|
|
impl From<AttributesError> for XmlReaderErr {
|
|
fn from(e: AttributesError) -> XmlReaderErr {
|
|
XmlReaderErr::AttributesError(e)
|
|
}
|
|
}
|
|
|
|
impl From<reader::Error> for XmlReaderErr {
|
|
fn from(e: reader::Error) -> XmlReaderErr {
|
|
XmlReaderErr::ReaderError(e)
|
|
}
|
|
}
|
|
|
|
impl From<DecodeError> for XmlReaderErr {
|
|
fn from(e: DecodeError) -> XmlReaderErr {
|
|
XmlReaderErr::Base64Error(e)
|
|
}
|
|
}
|
|
|
|
|
|
//------------ Attributes ----------------------------------------------------
|
|
|
|
/// A convenient wrapper for XML tag attributes
|
|
pub struct Attributes {
|
|
/// The underlying xml-rs structure
|
|
attributes: Vec<OwnedAttribute>
|
|
}
|
|
|
|
impl Attributes {
|
|
|
|
/// Takes an optional attribute by name
|
|
pub fn take_opt(&mut self, name: &str) -> Option<String> {
|
|
let i = self.attributes.iter().position(|a| a.name.local_name == name);
|
|
match i {
|
|
Some(i) => {
|
|
let a = self.attributes.swap_remove(i);
|
|
Some(a.value)
|
|
}
|
|
None => None
|
|
}
|
|
}
|
|
|
|
/// Takes an optional hexencoded attribute and converts it to Bytes
|
|
pub fn take_opt_hex(&mut self, name: &str) -> Option<Bytes> {
|
|
self.take_req_hex(name).ok()
|
|
}
|
|
|
|
/// Takes a required attribute by name
|
|
pub fn take_req(&mut self, name: &str) -> Result<String, AttributesError> {
|
|
self.take_opt(name)
|
|
.ok_or_else(|| AttributesError::MissingAttribute(name.to_string()))
|
|
}
|
|
|
|
/// Takes a required hexencoded attribute and converts it to Bytes
|
|
pub fn take_req_hex(&mut self, name: &str)
|
|
-> Result<Bytes, AttributesError> {
|
|
|
|
match hex::decode(self.take_req(name)?) {
|
|
Err(e) => Err(AttributesError::HexError(e)),
|
|
Ok(b) => Ok(Bytes::from(b))
|
|
}
|
|
}
|
|
|
|
/// Verifies that there are no more attributes
|
|
pub fn exhausted(&self) -> Result<(), AttributesError> {
|
|
if self.attributes.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
Err(AttributesError::ExtraAttributes)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//------------ AttributesError -----------------------------------------------
|
|
|
|
#[derive(Debug, Display)]
|
|
pub enum AttributesError {
|
|
#[display(fmt = "Required attribute missing: {}", _0)]
|
|
MissingAttribute(String),
|
|
|
|
#[display(fmt = "Extra attributes found")]
|
|
ExtraAttributes,
|
|
|
|
#[display(fmt = "Wrong hex encoding: {}", _0)]
|
|
HexError(FromHexError)
|
|
}
|
|
|
|
|
|
//------------ Tag -----------------------------------------------------------
|
|
|
|
pub struct Tag {
|
|
pub name: String
|
|
}
|
|
|
|
|
|
//------------ XmlWriter -----------------------------------------------------
|
|
|
|
/// A convenience wrapper for RPKI XML generation
|
|
///
|
|
/// This type only exposes things we need for the RPKI XML structures.
|
|
pub struct XmlWriter<W> {
|
|
/// The underlying xml-rs writer
|
|
writer: EventWriter<W>
|
|
}
|
|
|
|
|
|
/// Generate the XML.
|
|
impl <W: io::Write> XmlWriter<W> {
|
|
|
|
|
|
fn unwrap_emitter_error<T>(r: Result<T, writer::Error>) -> Result<T, io::Error> {
|
|
match r {
|
|
Ok(t) => Ok(t),
|
|
Err(e) => {
|
|
match e {
|
|
writer::Error::Io(io) => Err(io),
|
|
_ => {
|
|
// The other errors can only happen for stuff like
|
|
// not closing tags, starting a doc twice etc. But
|
|
// the XmlWriter lib already ensures that these things
|
|
// do not happen. They are not dependent on input.
|
|
panic!("XmlWriter library error: {:?}", e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds an element
|
|
pub fn put_element<F>(
|
|
&mut self,
|
|
name: &str,
|
|
attr: Option<&[(&str, &str)]>,
|
|
op: F) -> Result<(), io::Error>
|
|
where F: FnOnce(&mut Self) -> Result<(), io::Error> {
|
|
let mut start = writer::XmlEvent::start_element(name);
|
|
|
|
if let Some(v) = attr {
|
|
for a in v {
|
|
start = start.attr(a.0, a.1);
|
|
}
|
|
}
|
|
|
|
Self::unwrap_emitter_error(self.writer.write(start))?;
|
|
op(self)?;
|
|
Self::unwrap_emitter_error(
|
|
self.writer.write(writer::XmlEvent::end_element())
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Puts some String in a characters element
|
|
pub fn put_text(&mut self, text: &str) -> Result<(), io::Error> {
|
|
Self::unwrap_emitter_error(
|
|
self.writer.write(writer::XmlEvent::Characters(text))
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Converts bytes to base64 encoded Characters as the content. Note
|
|
/// that you cannot have both Characters and other included elements.
|
|
/// This would be valid XML, but it's not used by any of the RPKI XML
|
|
/// structures.
|
|
pub fn put_blob(&mut self, bytes: &Bytes) -> Result<(), io::Error> {
|
|
let b64 = base64::encode(bytes);
|
|
self.put_text(b64.as_ref())
|
|
}
|
|
|
|
/// Use this for convenience where empty content is required
|
|
pub fn empty(&mut self) -> Result<(), io::Error> {
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets up the writer config and returns a closure that is expected
|
|
/// to add the actual content of the XML.
|
|
///
|
|
/// This method is private because one should use the pub encode_vec
|
|
/// method, and in future others like it, to set up the writer for a
|
|
/// specific type (Vec<u8>, File, etc.).
|
|
fn encode<F>(w: W, op: F) -> Result<(), io::Error>
|
|
where F: FnOnce(&mut Self) -> Result<(), io::Error> {
|
|
|
|
let writer = EmitterConfig::new()
|
|
.write_document_declaration(false)
|
|
.normalize_empty_elements(true)
|
|
.perform_indent(true)
|
|
.create_writer(w);
|
|
|
|
let mut x = XmlWriter { writer };
|
|
|
|
op(&mut x)
|
|
}
|
|
}
|
|
|
|
impl XmlWriter<()> {
|
|
|
|
/// Call this to encode XML into a Vec<u8>
|
|
pub fn encode_vec<F>(op: F) -> Vec<u8>
|
|
where F: FnOnce(&mut XmlWriter<&mut Vec<u8>>)
|
|
-> Result<(), io::Error>
|
|
{
|
|
let mut b = Vec::new();
|
|
XmlWriter::encode(&mut b, op).unwrap(); // IO error impossible for vec
|
|
b
|
|
}
|
|
|
|
pub fn encode_to_file<F>(file: &mut File, op: F) -> Result<(), io::Error>
|
|
where F: FnOnce(&mut XmlWriter<&mut File>) -> Result<(), io::Error> {
|
|
XmlWriter::encode(file, op)
|
|
}
|
|
}
|
|
|
|
|
|
//------------ Tests ---------------------------------------------------------
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use std::str;
|
|
|
|
#[test]
|
|
fn should_write_xml() {
|
|
|
|
let xml = XmlWriter::encode_vec(|w| {
|
|
w.put_element(
|
|
"a",
|
|
Some(&[
|
|
("xmlns", "http://ns/"),
|
|
("c", "d")
|
|
]),
|
|
|w| {
|
|
w.put_element("b", None, |w| {
|
|
w.put_blob(&Bytes::from("X"))
|
|
})
|
|
}
|
|
)
|
|
});
|
|
|
|
assert_eq!(
|
|
str::from_utf8(&xml).unwrap(),
|
|
"<a xmlns=\"http://ns/\" c=\"d\">\n <b>WA==</b>\n</a>"
|
|
);
|
|
}
|
|
}
|