Skip to main content

rsvg/
error.rs

1//! Error types.
2
3use std::error;
4use std::fmt;
5
6use cssparser::{BasicParseError, BasicParseErrorKind, ParseErrorKind, ToCss};
7use markup5ever::QualName;
8
9#[cfg(doc)]
10use crate::RenderingError;
11
12use crate::document::NodeId;
13use crate::io::IoError;
14use crate::limits;
15use crate::node::Node;
16
17/// A short-lived error.
18///
19/// The lifetime of the error is the same as the `cssparser::ParserInput` that
20/// was used to create a `cssparser::Parser`.  That is, it is the lifetime of
21/// the string data that is being parsed.
22///
23/// The code flow will sometimes require preserving this error as a long-lived struct;
24/// see the `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>` for that
25/// purpose.
26pub type ParseError<'i> = cssparser::ParseError<'i, ValueErrorKind>;
27
28/// A simple error which refers to an attribute's value
29#[derive(Debug, Clone)]
30pub enum ValueErrorKind {
31    /// A property with the specified name was not found
32    UnknownProperty,
33
34    /// The value could not be parsed
35    Parse(String),
36
37    // The value could be parsed, but is invalid
38    Value(String),
39}
40
41impl ValueErrorKind {
42    pub fn parse_error(s: &str) -> ValueErrorKind {
43        ValueErrorKind::Parse(s.to_string())
44    }
45
46    pub fn value_error(s: &str) -> ValueErrorKind {
47        ValueErrorKind::Value(s.to_string())
48    }
49}
50
51impl fmt::Display for ValueErrorKind {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match *self {
54            ValueErrorKind::UnknownProperty => write!(f, "unknown property name"),
55
56            ValueErrorKind::Parse(ref s) => write!(f, "parse error: {s}"),
57
58            ValueErrorKind::Value(ref s) => write!(f, "invalid value: {s}"),
59        }
60    }
61}
62
63impl<'a> From<BasicParseError<'a>> for ValueErrorKind {
64    fn from(e: BasicParseError<'_>) -> ValueErrorKind {
65        let BasicParseError { kind, .. } = e;
66
67        let msg = match kind {
68            BasicParseErrorKind::UnexpectedToken(_) => "unexpected token",
69            BasicParseErrorKind::EndOfInput => "unexpected end of input",
70            BasicParseErrorKind::AtRuleInvalid(_) => "invalid @-rule",
71            BasicParseErrorKind::AtRuleBodyInvalid => "invalid @-rule body",
72            BasicParseErrorKind::QualifiedRuleInvalid => "invalid qualified rule",
73        };
74
75        ValueErrorKind::parse_error(msg)
76    }
77}
78
79/// A complete error for an attribute and its erroneous value
80#[derive(Debug, Clone)]
81pub struct ElementError {
82    pub attr: QualName,
83    pub err: ValueErrorKind,
84}
85
86impl fmt::Display for ElementError {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "{:?}: {}", self.attr.expanded(), self.err)
89    }
90}
91
92/// Errors returned when looking up a resource by URL reference.
93#[derive(Debug, Clone)]
94pub enum DefsLookupErrorKind {
95    /// Error when parsing the id to lookup.
96    InvalidId,
97
98    /// For internal use only.
99    ///
100    // FIXME: this is returned internally from Handle.lookup_node(), and gets translated
101    // to Ok(false).  Don't expose this internal code in the public API.
102    NotFound,
103}
104
105impl fmt::Display for DefsLookupErrorKind {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match *self {
108            DefsLookupErrorKind::InvalidId => write!(f, "invalid id"),
109            DefsLookupErrorKind::NotFound => write!(f, "not found"),
110        }
111    }
112}
113
114/// Errors that can happen while rendering or measuring an SVG document.
115///
116/// This is the internal version of [`crate::api::RenderingError`]; they are the same
117/// except that this one has an `InvalidTransform` variant which is only propagated
118/// internally.  It is caught during the drawing process, and the element in question
119/// is simply not drawn, more or less per <https://www.w3.org/TR/css-transforms-1/#transform-function-lists>
120///
121///   "If a transform function causes the current transformation matrix of an
122///   object to be non-invertible, the object and its content do not get
123///   displayed."
124#[derive(Clone)]
125pub enum InternalRenderingError {
126    /// An error from the rendering backend.
127    Rendering(String),
128
129    /// A particular implementation-defined limit was exceeded.
130    LimitExceeded(ImplementationLimit),
131
132    /// A non-invertible transform was generated.
133    ///
134    /// This should not be a fatal error; we should catch it and just not render
135    /// the problematic element.
136    InvalidTransform,
137
138    CircularReference(Node),
139
140    /// Tried to reference an SVG element that does not exist.
141    IdNotFound,
142
143    /// Tried to reference an SVG element from a fragment identifier that is incorrect.
144    InvalidId(String),
145
146    /// Not enough memory was available for rendering.
147    OutOfMemory(String),
148
149    /// The rendering was interrupted via a [`gio::Cancellable`].
150    Cancelled,
151}
152
153impl From<DefsLookupErrorKind> for InternalRenderingError {
154    fn from(e: DefsLookupErrorKind) -> InternalRenderingError {
155        match e {
156            DefsLookupErrorKind::NotFound => InternalRenderingError::IdNotFound,
157            _ => InternalRenderingError::InvalidId(format!("{e}")),
158        }
159    }
160}
161
162impl From<InvalidTransform> for InternalRenderingError {
163    fn from(_: InvalidTransform) -> InternalRenderingError {
164        InternalRenderingError::InvalidTransform
165    }
166}
167
168impl fmt::Display for InternalRenderingError {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match *self {
171            InternalRenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
172            InternalRenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
173            InternalRenderingError::InvalidTransform => write!(f, "invalid transform"),
174            InternalRenderingError::CircularReference(ref c) => {
175                write!(f, "circular reference in element {c}")
176            }
177            InternalRenderingError::IdNotFound => write!(f, "element id not found"),
178            InternalRenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
179            InternalRenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
180            InternalRenderingError::Cancelled => write!(f, "rendering cancelled"),
181        }
182    }
183}
184
185impl From<cairo::Error> for InternalRenderingError {
186    fn from(e: cairo::Error) -> InternalRenderingError {
187        InternalRenderingError::Rendering(format!("{e:?}"))
188    }
189}
190
191macro_rules! box_error {
192    ($from_ty:ty) => {
193        impl From<$from_ty> for Box<InternalRenderingError> {
194            fn from(e: $from_ty) -> Box<InternalRenderingError> {
195                Box::new(e.into())
196            }
197        }
198    };
199}
200
201box_error!(DefsLookupErrorKind);
202box_error!(InvalidTransform);
203box_error!(cairo::Error);
204
205/// Indicates that a transform is not invertible.
206///
207/// This generally represents an error from [`crate::transform::ValidTransform::try_from`], which is what we use
208/// to check affine transforms for validity.
209#[derive(Debug, PartialEq)]
210pub struct InvalidTransform;
211
212impl fmt::Display for InvalidTransform {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        write!(f, "invalid transform")
215    }
216}
217
218/// Errors from [`crate::document::AcquiredNodes`].
219pub enum AcquireError {
220    /// An element with the specified id was not found.
221    LinkNotFound(NodeId),
222
223    InvalidLinkType(NodeId),
224
225    /// A circular reference was detected; non-fatal error.
226    ///
227    /// Callers are expected to treat the offending element as invalid, for example
228    /// if a graphic element uses a pattern fill, but the pattern in turn includes
229    /// another graphic element that references the same pattern.
230    ///
231    /// ```xml
232    /// <pattern id="foo">
233    ///   <rect width="1" height="1" fill="url(#foo)"/>
234    /// </pattern>
235    /// ```
236    CircularReference(Node),
237
238    /// Too many referenced objects were resolved; fatal error.
239    ///
240    /// Callers are expected to exit as early as possible and return an error to
241    /// the public API.  See [`ImplementationLimit::TooManyReferencedElements`] for details.
242    MaxReferencesExceeded,
243}
244
245impl fmt::Display for AcquireError {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match *self {
248            AcquireError::LinkNotFound(ref frag) => write!(f, "link not found: {frag}"),
249
250            AcquireError::InvalidLinkType(ref frag) => {
251                write!(f, "link \"{frag}\" is to object of invalid type")
252            }
253
254            AcquireError::CircularReference(ref node) => {
255                write!(f, "circular reference in node {node}")
256            }
257
258            AcquireError::MaxReferencesExceeded => {
259                write!(f, "maximum number of references exceeded")
260            }
261        }
262    }
263}
264
265/// Error returned when the depth of loaded files exceeds a limit.
266///
267/// This is returned when [`crate::document::LoadingDepthLimiter`] detects that the
268/// maximum depth of recursively loaded files exceeds [`limits::MAX_FILE_LOADING_DEPTH`].
269pub struct LoadingDepthError;
270
271/// Helper for converting `Result<O, E>` into `Result<O, ElementError>`
272///
273/// A `ElementError` requires a `QualName` that corresponds to the attribute to which the
274/// error refers, plus the actual `ValueErrorKind` that describes the error.  However,
275/// parsing functions for attribute value types will want to return their own kind of
276/// error, instead of `ValueErrorKind`.  If that particular error type has an `impl
277/// From<FooError> for ValueErrorKind`, then this trait helps assign attribute values in
278/// `set_atts()` methods as follows:
279///
280/// ```
281/// # use rsvg::doctest_only::AttributeResultExt;
282/// # use rsvg::doctest_only::ValueErrorKind;
283/// # use rsvg::doctest_only::ElementError;
284/// # use markup5ever::{QualName, Prefix, Namespace, LocalName};
285/// # type FooError = ValueErrorKind;
286/// fn parse_foo(value: &str) -> Result<(), FooError>
287/// # { Err(ValueErrorKind::value_error("test")) }
288///
289/// // It is assumed that there is an impl From<FooError> for ValueErrorKind
290/// # let attr = QualName::new(
291/// #     Some(Prefix::from("")),
292/// #     Namespace::from(""),
293/// #     LocalName::from(""),
294/// # );
295/// let result = parse_foo("value").attribute(attr);
296/// assert!(result.is_err());
297/// # Ok::<(), ElementError>(())
298/// ```
299///
300/// The call to `.attribute(attr)` converts the `Result` from `parse_foo()` into a full
301/// `ElementError` with the provided `attr`.
302pub trait AttributeResultExt<O> {
303    fn attribute(self, attr: QualName) -> Result<O, ElementError>;
304}
305
306impl<O, E: Into<ValueErrorKind>> AttributeResultExt<O> for Result<O, E> {
307    fn attribute(self, attr: QualName) -> Result<O, ElementError> {
308        self.map_err(|e| e.into())
309            .map_err(|err| ElementError { attr, err })
310    }
311}
312
313/// Convert a short-lived ParseError into a long-lived ElementError
314///
315/// We extract this as a function, instead of putting it directly in the `map_err` invocation
316/// below in `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>`, because
317/// putting it there as a closure generates too many duplicated copies of the code.  The generic
318/// parameter `O` in that `impl` is for the result `Ok()` value, not for the `Err`, after all.
319fn parse_error_to_element_error<'i>(e: ParseError<'i>, attr: QualName) -> ElementError {
320    // FIXME: eventually, here we'll want to preserve the location information
321
322    let ParseError {
323        kind,
324        location: _location,
325    } = e;
326
327    match kind {
328        ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(tok)) => {
329            let mut s = String::from("unexpected token '");
330            tok.to_css(&mut s).unwrap(); // FIXME: what do we do with a fmt::Error?
331            s.push('\'');
332
333            ElementError {
334                attr,
335                err: ValueErrorKind::Parse(s),
336            }
337        }
338
339        ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => ElementError {
340            attr,
341            err: ValueErrorKind::parse_error("unexpected end of input"),
342        },
343
344        ParseErrorKind::Basic(_) => {
345            unreachable!("attribute parsers should not return errors for CSS rules")
346        }
347
348        ParseErrorKind::Custom(err) => ElementError { attr, err },
349    }
350}
351
352/// Turns a short-lived `ParseError` into a long-lived `ElementError`
353impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>> {
354    fn attribute(self, attr: QualName) -> Result<O, ElementError> {
355        self.map_err(|e| parse_error_to_element_error(e, attr))
356    }
357}
358
359/// Errors returned when resolving an URL
360#[derive(Debug, Clone)]
361pub enum AllowedUrlError {
362    /// parsing error from `Url::parse()`
363    UrlParseError(url::ParseError),
364
365    /// A base file/uri was not set
366    BaseRequired,
367
368    /// Cannot reference a file with a different URI scheme from the base file
369    DifferentUriSchemes,
370
371    /// Some scheme we don't allow loading
372    DisallowedScheme,
373
374    /// The requested file is not in the same directory as the base file,
375    /// or in one directory below the base file.
376    NotSiblingOrChildOfBaseFile,
377
378    /// Loaded file:// URLs cannot have a query part, e.g. `file:///foo?blah`
379    NoQueriesAllowed,
380
381    /// URLs may not have fragment identifiers at this stage
382    NoFragmentIdentifierAllowed,
383
384    /// `file:` URLs may not have a hostname
385    NoHostAllowed,
386
387    /// Error when obtaining the file path that corresponds to the URL
388    InvalidPathInUrl,
389
390    /// Error when obtaining the file path that corresponds to the base URL
391    InvalidPathInBaseUrl,
392
393    /// The base file cannot be the root of the file system
394    BaseIsRoot,
395
396    /// Error when canonicalizing either the file path or the base file path
397    CanonicalizationError,
398}
399
400impl fmt::Display for AllowedUrlError {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        use AllowedUrlError::*;
403        match *self {
404            UrlParseError(e) => write!(f, "URL parse error: {e}"),
405            BaseRequired => write!(f, "base required"),
406            DifferentUriSchemes => write!(f, "different URI schemes"),
407            DisallowedScheme => write!(f, "disallowed scheme"),
408            NotSiblingOrChildOfBaseFile => write!(f, "not sibling or child of base file"),
409            NoQueriesAllowed => write!(f, "no queries allowed"),
410            NoFragmentIdentifierAllowed => write!(f, "no fragment identifier allowed"),
411            NoHostAllowed => write!(f, "no hostnames allowed"),
412            InvalidPathInUrl => write!(f, "invalid path in file URL"),
413            InvalidPathInBaseUrl => write!(f, "invalid path in base URL"),
414            BaseIsRoot => write!(f, "base is root"),
415            CanonicalizationError => write!(f, "canonicalization error"),
416        }
417    }
418}
419
420/// Errors returned when creating a `NodeId` out of a string
421#[derive(Debug, Clone)]
422pub enum NodeIdError {
423    NodeIdRequired,
424}
425
426impl From<NodeIdError> for ValueErrorKind {
427    fn from(e: NodeIdError) -> ValueErrorKind {
428        match e {
429            NodeIdError::NodeIdRequired => {
430                ValueErrorKind::value_error("fragment identifier required")
431            }
432        }
433    }
434}
435
436/// Errors that can happen while loading an SVG document.
437///
438/// All of these codes are for unrecoverable errors that keep an SVG document from being
439/// fully loaded and parsed.  Note that SVG is very lenient with respect to document
440/// structure and the syntax of CSS property values; most errors there will not lead to a
441/// `LoadingError`.  To see those errors, you may want to set the `RSVG_LOG=1` environment
442/// variable.
443///
444/// I/O errors get reported in the `Glib` variant, since librsvg uses GIO internally for
445/// all input/output.
446#[non_exhaustive]
447#[derive(Debug, Clone)]
448pub enum LoadingError {
449    /// XML syntax error.
450    XmlParseError(String),
451
452    /// Not enough memory to load the document.
453    OutOfMemory(String),
454
455    /// A malformed or disallowed URL was used.
456    BadUrl,
457
458    /// An invalid stylesheet was used.
459    BadCss,
460
461    /// There is no `<svg>` root element in the XML.
462    NoSvgRoot,
463
464    /// I/O error.
465    Io(String),
466
467    /// A particular implementation-defined limit was exceeded.
468    LimitExceeded(ImplementationLimit),
469
470    /// Catch-all for loading errors.
471    Other(String),
472}
473
474/// Errors for implementation-defined limits, to mitigate malicious SVG documents.
475///
476/// These get emitted as [`LoadingError::LimitExceeded`] or [`RenderingError::LimitExceeded`].
477/// The limits are present to mitigate malicious SVG documents which may try to exhaust
478/// all available memory, or which would use large amounts of CPU time.
479#[non_exhaustive]
480#[derive(Debug, Copy, Clone)]
481pub enum ImplementationLimit {
482    /// Document exceeded the maximum number of times that elements
483    /// can be referenced through URL fragments.
484    ///
485    /// This is a mitigation for malicious documents that attempt to
486    /// consume exponential amounts of CPU time by creating millions
487    /// of references to SVG elements.  For example, the `<use>` and
488    /// `<pattern>` elements allow referencing other elements, which
489    /// can in turn reference other elements.  This can be used to
490    /// create documents which would require exponential amounts of
491    /// CPU time to be rendered.
492    ///
493    /// Librsvg deals with both cases by placing a limit on how many
494    /// references will be resolved during the SVG rendering process,
495    /// that is, how many `url(#foo)` will be resolved.
496    ///
497    /// These malicious documents are similar to the XML
498    /// [billion laughs attack], but done with SVG's referencing features.
499    ///
500    /// See issues
501    /// [#323](https://gitlab.gnome.org/GNOME/librsvg/issues/323) and
502    /// [#515](https://gitlab.gnome.org/GNOME/librsvg/issues/515) for
503    /// examples for the `<use>` and `<pattern>` elements,
504    /// respectively.
505    ///
506    /// [billion laughs attack]: https://bitbucket.org/tiran/defusedxml
507    TooManyReferencedElements,
508
509    /// Document exceeded the maximum number of elements that can be loaded.
510    ///
511    /// This is a mitigation for SVG files which create millions of
512    /// elements in an attempt to exhaust memory.  Librsvg does not't
513    /// allow loading more than a certain number of elements during
514    /// the initial loading process.
515    TooManyLoadedElements,
516
517    /// Document exceeded the number of attributes that can be attached to
518    /// an element.
519    ///
520    /// This is here because librsvg uses u16 to address attributes. It should
521    /// be essentially impossible to actually hit this limit, because the
522    /// number of attributes that the SVG standard ascribes meaning to are
523    /// lower than this limit.
524    TooManyAttributes,
525
526    /// Document exceeded the maximum nesting level while rendering.
527    ///
528    /// Rendering is a recursive process, and there is a limit of how deep layers can
529    /// nest.  This is to avoid malicious SVGs which try to have layers that are nested
530    /// extremely deep, as this could cause stack exhaustion.
531    MaximumLayerNestingDepthExceeded,
532
533    /// Nesting level of referenced files exceeded the maximum allowed.
534    ///
535    /// Loading or rendering a document may cause other SVG documents or files
536    /// to be loaded, and in turn those other files may request further files
537    /// on their own.  Librsvg will limit the maximum depth of nesting for
538    /// loaded files, for XInclude, referencing documents via `<image>`,
539    /// CSS includes, etc., and also to deal with files that recursively reference
540    /// themselves.
541    MaximumFileNestingDepthExceeded,
542}
543
544impl error::Error for LoadingError {}
545
546impl fmt::Display for LoadingError {
547    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548        match *self {
549            LoadingError::XmlParseError(ref s) => write!(f, "XML parse error: {s}"),
550            LoadingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
551            LoadingError::BadUrl => write!(f, "invalid URL"),
552            LoadingError::BadCss => write!(f, "invalid CSS"),
553            LoadingError::NoSvgRoot => write!(f, "XML does not have <svg> root"),
554            LoadingError::Io(ref s) => write!(f, "I/O error: {s}"),
555            LoadingError::LimitExceeded(ref l) => write!(f, "{l}"),
556            LoadingError::Other(ref s) => write!(f, "{s}"),
557        }
558    }
559}
560
561impl From<glib::Error> for LoadingError {
562    fn from(e: glib::Error) -> LoadingError {
563        // FIXME: this is somewhat fishy; not all GError are I/O errors, but in librsvg
564        // most GError do come from gio.  Some come from GdkPixbufLoader, though.
565        LoadingError::Io(format!("{e}"))
566    }
567}
568
569impl From<IoError> for LoadingError {
570    fn from(e: IoError) -> LoadingError {
571        match e {
572            IoError::BadDataUrl => LoadingError::BadUrl,
573            IoError::Glib(e) => LoadingError::Io(format!("{e}")),
574        }
575    }
576}
577
578impl From<LoadingDepthError> for LoadingError {
579    fn from(e: LoadingDepthError) -> LoadingError {
580        match e {
581            LoadingDepthError => {
582                LoadingError::LimitExceeded(ImplementationLimit::MaximumFileNestingDepthExceeded)
583            }
584        }
585    }
586}
587
588impl fmt::Display for ImplementationLimit {
589    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590        match *self {
591            ImplementationLimit::TooManyReferencedElements => write!(
592                f,
593                "exceeded more than {} referenced elements",
594                limits::MAX_REFERENCED_ELEMENTS
595            ),
596
597            ImplementationLimit::TooManyLoadedElements => write!(
598                f,
599                "cannot load more than {} XML elements",
600                limits::MAX_LOADED_ELEMENTS
601            ),
602
603            ImplementationLimit::TooManyAttributes => write!(
604                f,
605                "cannot load more than {} XML attributes",
606                limits::MAX_LOADED_ATTRIBUTES
607            ),
608
609            ImplementationLimit::MaximumLayerNestingDepthExceeded => write!(
610                f,
611                "maximum depth of {} nested layers has been exceeded",
612                limits::MAX_LAYER_NESTING_DEPTH,
613            ),
614
615            ImplementationLimit::MaximumFileNestingDepthExceeded => write!(
616                f,
617                "maximum depth of {} nested files exceeded when loading",
618                limits::MAX_FILE_LOADING_DEPTH,
619            ),
620        }
621    }
622}