summaryrefslogtreecommitdiff
path: root/rsvg/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsvg/src/error.rs')
-rw-r--r--rsvg/src/error.rs526
1 files changed, 526 insertions, 0 deletions
diff --git a/rsvg/src/error.rs b/rsvg/src/error.rs
new file mode 100644
index 00000000..ffa1e690
--- /dev/null
+++ b/rsvg/src/error.rs
@@ -0,0 +1,526 @@
+//! Error types.
+
+use std::error;
+use std::fmt;
+
+use cssparser::{BasicParseError, BasicParseErrorKind, ParseErrorKind, ToCss};
+use markup5ever::QualName;
+
+use crate::document::NodeId;
+use crate::io::IoError;
+use crate::limits;
+use crate::node::Node;
+
+/// A short-lived error.
+///
+/// The lifetime of the error is the same as the `cssparser::ParserInput` that
+/// was used to create a `cssparser::Parser`. That is, it is the lifetime of
+/// the string data that is being parsed.
+///
+/// The code flow will sometimes require preserving this error as a long-lived struct;
+/// see the `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>` for that
+/// purpose.
+pub type ParseError<'i> = cssparser::ParseError<'i, ValueErrorKind>;
+
+/// A simple error which refers to an attribute's value
+#[derive(Debug, Clone)]
+pub enum ValueErrorKind {
+ /// A property with the specified name was not found
+ UnknownProperty,
+
+ /// The value could not be parsed
+ Parse(String),
+
+ // The value could be parsed, but is invalid
+ Value(String),
+}
+
+impl ValueErrorKind {
+ pub fn parse_error(s: &str) -> ValueErrorKind {
+ ValueErrorKind::Parse(s.to_string())
+ }
+
+ pub fn value_error(s: &str) -> ValueErrorKind {
+ ValueErrorKind::Value(s.to_string())
+ }
+}
+
+impl fmt::Display for ValueErrorKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ ValueErrorKind::UnknownProperty => write!(f, "unknown property name"),
+
+ ValueErrorKind::Parse(ref s) => write!(f, "parse error: {s}"),
+
+ ValueErrorKind::Value(ref s) => write!(f, "invalid value: {s}"),
+ }
+ }
+}
+
+impl<'a> From<BasicParseError<'a>> for ValueErrorKind {
+ fn from(e: BasicParseError<'_>) -> ValueErrorKind {
+ let BasicParseError { kind, .. } = e;
+
+ let msg = match kind {
+ BasicParseErrorKind::UnexpectedToken(_) => "unexpected token",
+ BasicParseErrorKind::EndOfInput => "unexpected end of input",
+ BasicParseErrorKind::AtRuleInvalid(_) => "invalid @-rule",
+ BasicParseErrorKind::AtRuleBodyInvalid => "invalid @-rule body",
+ BasicParseErrorKind::QualifiedRuleInvalid => "invalid qualified rule",
+ };
+
+ ValueErrorKind::parse_error(msg)
+ }
+}
+
+/// A complete error for an attribute and its erroneous value
+#[derive(Debug, Clone)]
+pub struct ElementError {
+ pub attr: QualName,
+ pub err: ValueErrorKind,
+}
+
+impl fmt::Display for ElementError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}: {}", self.attr.expanded(), self.err)
+ }
+}
+
+/// Errors returned when looking up a resource by URL reference.
+#[derive(Debug, Clone)]
+pub enum DefsLookupErrorKind {
+ /// Error when parsing the id to lookup.
+ InvalidId,
+
+ /// Used when the public API tries to look up an external URL, which is not allowed.
+ ///
+ /// This catches the case where a public API wants to be misused to access an external
+ /// resource. For example, `SvgHandle.has_sub("https://evil.com/phone_home#element_id")` will
+ /// fail with this error.
+ CannotLookupExternalReferences,
+
+ /// For internal use only.
+ ///
+ // FIXME: this is returned internally from Handle.lookup_node(), and gets translated
+ // to Ok(false). Don't expose this internal code in the public API.
+ NotFound,
+}
+
+impl fmt::Display for DefsLookupErrorKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ DefsLookupErrorKind::InvalidId => write!(f, "invalid id"),
+ DefsLookupErrorKind::CannotLookupExternalReferences => {
+ write!(f, "cannot lookup references to elements in external files")
+ }
+ DefsLookupErrorKind::NotFound => write!(f, "not found"),
+ }
+ }
+}
+
+/// Errors that can happen while rendering or measuring an SVG document.
+#[non_exhaustive]
+#[derive(Debug, Clone)]
+pub enum RenderingError {
+ /// An error from the rendering backend.
+ Rendering(String),
+
+ /// A particular implementation-defined limit was exceeded.
+ LimitExceeded(ImplementationLimit),
+
+ /// A non-invertible transform was generated.
+ ///
+ /// This should not be a fatal error; we should catch it and just not render
+ /// the problematic element.
+ InvalidTransform,
+
+ /// Tried to reference an SVG element that does not exist.
+ IdNotFound,
+
+ /// Tried to reference an SVG element from a fragment identifier that is incorrect.
+ InvalidId(String),
+
+ /// Not enough memory was available for rendering.
+ OutOfMemory(String),
+}
+
+impl From<DefsLookupErrorKind> for RenderingError {
+ fn from(e: DefsLookupErrorKind) -> RenderingError {
+ match e {
+ DefsLookupErrorKind::NotFound => RenderingError::IdNotFound,
+ _ => RenderingError::InvalidId(format!("{e}")),
+ }
+ }
+}
+
+impl From<InvalidTransform> for RenderingError {
+ fn from(_: InvalidTransform) -> RenderingError {
+ RenderingError::InvalidTransform
+ }
+}
+
+impl error::Error for RenderingError {}
+
+impl fmt::Display for RenderingError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ RenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
+ RenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
+ RenderingError::InvalidTransform => write!(f, "invalid transform"),
+ RenderingError::IdNotFound => write!(f, "element id not found"),
+ RenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
+ RenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
+ }
+ }
+}
+
+impl From<cairo::Error> for RenderingError {
+ fn from(e: cairo::Error) -> RenderingError {
+ RenderingError::Rendering(format!("{e:?}"))
+ }
+}
+
+/// Indicates that a transform is not invertible.
+///
+/// This generally represents an error from [`ValidTransform::try_from`], which is what we use
+/// to check affine transforms for validity.
+#[derive(Debug, PartialEq)]
+pub struct InvalidTransform;
+
+/// Errors from [`crate::document::AcquiredNodes`].
+pub enum AcquireError {
+ /// An element with the specified id was not found.
+ LinkNotFound(NodeId),
+
+ InvalidLinkType(NodeId),
+
+ /// A circular reference was detected; non-fatal error.
+ ///
+ /// Callers are expected to treat the offending element as invalid, for example
+ /// if a graphic element uses a pattern fill, but the pattern in turn includes
+ /// another graphic element that references the same pattern.
+ ///
+ /// ```xml
+ /// <pattern id="foo">
+ /// <rect width="1" height="1" fill="url(#foo)"/>
+ /// </pattern>
+ /// ```
+ CircularReference(Node),
+
+ /// Too many referenced objects were resolved; fatal error.
+ ///
+ /// Callers are expected to exit as early as possible and return an error to
+ /// the public API. See [`ImplementationLimit::TooManyReferencedElements`] for details.
+ MaxReferencesExceeded,
+}
+
+impl fmt::Display for AcquireError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ AcquireError::LinkNotFound(ref frag) => write!(f, "link not found: {frag}"),
+
+ AcquireError::InvalidLinkType(ref frag) => {
+ write!(f, "link \"{frag}\" is to object of invalid type")
+ }
+
+ AcquireError::CircularReference(ref node) => {
+ write!(f, "circular reference in node {node}")
+ }
+
+ AcquireError::MaxReferencesExceeded => {
+ write!(f, "maximum number of references exceeded")
+ }
+ }
+ }
+}
+
+/// Helper for converting `Result<O, E>` into `Result<O, ElementError>`
+///
+/// A `ElementError` requires a `QualName` that corresponds to the attribute to which the
+/// error refers, plus the actual `ValueErrorKind` that describes the error. However,
+/// parsing functions for attribute value types will want to return their own kind of
+/// error, instead of `ValueErrorKind`. If that particular error type has an `impl
+/// From<FooError> for ValueErrorKind`, then this trait helps assign attribute values in
+/// `set_atts()` methods as follows:
+///
+/// ```
+/// # use rsvg::doctest_only::AttributeResultExt;
+/// # use rsvg::doctest_only::ValueErrorKind;
+/// # use rsvg::doctest_only::ElementError;
+/// # use markup5ever::{QualName, Prefix, Namespace, LocalName};
+/// # type FooError = ValueErrorKind;
+/// fn parse_foo(value: &str) -> Result<(), FooError>
+/// # { Err(ValueErrorKind::value_error("test")) }
+///
+/// // It is assumed that there is an impl From<FooError> for ValueErrorKind
+/// # let attr = QualName::new(
+/// # Some(Prefix::from("")),
+/// # Namespace::from(""),
+/// # LocalName::from(""),
+/// # );
+/// let result = parse_foo("value").attribute(attr);
+/// assert!(result.is_err());
+/// # Ok::<(), ElementError>(())
+/// ```
+///
+/// The call to `.attribute(attr)` converts the `Result` from `parse_foo()` into a full
+/// `ElementError` with the provided `attr`.
+pub trait AttributeResultExt<O> {
+ fn attribute(self, attr: QualName) -> Result<O, ElementError>;
+}
+
+impl<O, E: Into<ValueErrorKind>> AttributeResultExt<O> for Result<O, E> {
+ fn attribute(self, attr: QualName) -> Result<O, ElementError> {
+ self.map_err(|e| e.into())
+ .map_err(|err| ElementError { attr, err })
+ }
+}
+
+/// Turns a short-lived `ParseError` into a long-lived `ElementError`
+impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>> {
+ fn attribute(self, attr: QualName) -> Result<O, ElementError> {
+ self.map_err(|e| {
+ // FIXME: eventually, here we'll want to preserve the location information
+
+ let ParseError {
+ kind,
+ location: _location,
+ } = e;
+
+ match kind {
+ ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(tok)) => {
+ let mut s = String::from("unexpected token '");
+ tok.to_css(&mut s).unwrap(); // FIXME: what do we do with a fmt::Error?
+ s.push('\'');
+
+ ElementError {
+ attr,
+ err: ValueErrorKind::Parse(s),
+ }
+ }
+
+ ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => ElementError {
+ attr,
+ err: ValueErrorKind::parse_error("unexpected end of input"),
+ },
+
+ ParseErrorKind::Basic(_) => {
+ unreachable!("attribute parsers should not return errors for CSS rules")
+ }
+
+ ParseErrorKind::Custom(err) => ElementError { attr, err },
+ }
+ })
+ }
+}
+
+/// Errors returned when resolving an URL
+#[derive(Debug, Clone)]
+pub enum AllowedUrlError {
+ /// parsing error from `Url::parse()`
+ UrlParseError(url::ParseError),
+
+ /// A base file/uri was not set
+ BaseRequired,
+
+ /// Cannot reference a file with a different URI scheme from the base file
+ DifferentUriSchemes,
+
+ /// Some scheme we don't allow loading
+ DisallowedScheme,
+
+ /// The requested file is not in the same directory as the base file,
+ /// or in one directory below the base file.
+ NotSiblingOrChildOfBaseFile,
+
+ /// Error when obtaining the file path or the base file path
+ InvalidPath,
+
+ /// The base file cannot be the root of the file system
+ BaseIsRoot,
+
+ /// Error when canonicalizing either the file path or the base file path
+ CanonicalizationError,
+}
+
+impl fmt::Display for AllowedUrlError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ AllowedUrlError::UrlParseError(e) => write!(f, "URL parse error: {e}"),
+ AllowedUrlError::BaseRequired => write!(f, "base required"),
+ AllowedUrlError::DifferentUriSchemes => write!(f, "different URI schemes"),
+ AllowedUrlError::DisallowedScheme => write!(f, "disallowed scheme"),
+ AllowedUrlError::NotSiblingOrChildOfBaseFile => {
+ write!(f, "not sibling or child of base file")
+ }
+ AllowedUrlError::InvalidPath => write!(f, "invalid path"),
+ AllowedUrlError::BaseIsRoot => write!(f, "base is root"),
+ AllowedUrlError::CanonicalizationError => write!(f, "canonicalization error"),
+ }
+ }
+}
+
+/// Errors returned when creating a `NodeId` out of a string
+#[derive(Debug, Clone)]
+pub enum NodeIdError {
+ NodeIdRequired,
+}
+
+impl From<NodeIdError> for ValueErrorKind {
+ fn from(e: NodeIdError) -> ValueErrorKind {
+ match e {
+ NodeIdError::NodeIdRequired => {
+ ValueErrorKind::value_error("fragment identifier required")
+ }
+ }
+ }
+}
+
+/// Errors that can happen while loading an SVG document.
+///
+/// All of these codes are for unrecoverable errors that keep an SVG document from being
+/// fully loaded and parsed. Note that SVG is very lenient with respect to document
+/// structure and the syntax of CSS property values; most errors there will not lead to a
+/// `LoadingError`. To see those errors, you may want to set the `RSVG_LOG=1` environment
+/// variable.
+///
+/// I/O errors get reported in the `Glib` variant, since librsvg uses GIO internally for
+/// all input/output.
+#[non_exhaustive]
+#[derive(Debug, Clone)]
+pub enum LoadingError {
+ /// XML syntax error.
+ XmlParseError(String),
+
+ /// Not enough memory to load the document.
+ OutOfMemory(String),
+
+ /// A malformed or disallowed URL was used.
+ BadUrl,
+
+ /// An invalid stylesheet was used.
+ BadCss,
+
+ /// There is no `<svg>` root element in the XML.
+ NoSvgRoot,
+
+ /// I/O error.
+ Io(String),
+
+ /// A particular implementation-defined limit was exceeded.
+ LimitExceeded(ImplementationLimit),
+
+ /// Catch-all for loading errors.
+ Other(String),
+}
+
+/// Errors for implementation-defined limits, to mitigate malicious SVG documents.
+///
+/// These get emitted as `LoadingError::LimitExceeded` or `RenderingError::LimitExceeded`.
+/// The limits are present to mitigate malicious SVG documents which may try to exhaust
+/// all available memory, or which would use large amounts of CPU time.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone)]
+pub enum ImplementationLimit {
+ /// Document exceeded the maximum number of times that elements
+ /// can be referenced through URL fragments.
+ ///
+ /// This is a mitigation for malicious documents that attempt to
+ /// consume exponential amounts of CPU time by creating millions
+ /// of references to SVG elements. For example, the `<use>` and
+ /// `<pattern>` elements allow referencing other elements, which
+ /// can in turn reference other elements. This can be used to
+ /// create documents which would require exponential amounts of
+ /// CPU time to be rendered.
+ ///
+ /// Librsvg deals with both cases by placing a limit on how many
+ /// references will be resolved during the SVG rendering process,
+ /// that is, how many `url(#foo)` will be resolved.
+ ///
+ /// These malicious documents are similar to the XML
+ /// [billion laughs attack], but done with SVG's referencing features.
+ ///
+ /// See issues
+ /// [#323](https://gitlab.gnome.org/GNOME/librsvg/issues/323) and
+ /// [#515](https://gitlab.gnome.org/GNOME/librsvg/issues/515) for
+ /// examples for the `<use>` and `<pattern>` elements,
+ /// respectively.
+ ///
+ /// [billion laughs attack]: https://bitbucket.org/tiran/defusedxml
+ TooManyReferencedElements,
+
+ /// Document exceeded the maximum number of elements that can be loaded.
+ ///
+ /// This is a mitigation for SVG files which create millions of
+ /// elements in an attempt to exhaust memory. Librsvg does not't
+ /// allow loading more than a certain number of elements during
+ /// the initial loading process.
+ TooManyLoadedElements,
+
+ /// Document exceeded the number of attributes that can be attached to
+ /// an element.
+ ///
+ /// This is here because librsvg uses u16 to address attributes. It should
+ /// be essentially impossible to actually hit this limit, because the
+ /// number of attributes that the SVG standard ascribes meaning to are
+ /// lower than this limit.
+ TooManyAttributes,
+}
+
+impl error::Error for LoadingError {}
+
+impl fmt::Display for LoadingError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ LoadingError::XmlParseError(ref s) => write!(f, "XML parse error: {s}"),
+ LoadingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
+ LoadingError::BadUrl => write!(f, "invalid URL"),
+ LoadingError::BadCss => write!(f, "invalid CSS"),
+ LoadingError::NoSvgRoot => write!(f, "XML does not have <svg> root"),
+ LoadingError::Io(ref s) => write!(f, "I/O error: {s}"),
+ LoadingError::LimitExceeded(ref l) => write!(f, "{l}"),
+ LoadingError::Other(ref s) => write!(f, "{s}"),
+ }
+ }
+}
+
+impl From<glib::Error> for LoadingError {
+ fn from(e: glib::Error) -> LoadingError {
+ // FIXME: this is somewhat fishy; not all GError are I/O errors, but in librsvg
+ // most GError do come from gio. Some come from GdkPixbufLoader, though.
+ LoadingError::Io(format!("{e}"))
+ }
+}
+
+impl From<IoError> for LoadingError {
+ fn from(e: IoError) -> LoadingError {
+ match e {
+ IoError::BadDataUrl => LoadingError::BadUrl,
+ IoError::Glib(e) => LoadingError::Io(format!("{e}")),
+ }
+ }
+}
+
+impl fmt::Display for ImplementationLimit {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ ImplementationLimit::TooManyReferencedElements => write!(
+ f,
+ "exceeded more than {} referenced elements",
+ limits::MAX_REFERENCED_ELEMENTS
+ ),
+
+ ImplementationLimit::TooManyLoadedElements => write!(
+ f,
+ "cannot load more than {} XML elements",
+ limits::MAX_LOADED_ELEMENTS
+ ),
+
+ ImplementationLimit::TooManyAttributes => write!(
+ f,
+ "cannot load more than {} XML attributes",
+ limits::MAX_LOADED_ATTRIBUTES
+ ),
+ }
+ }
+}